-- UISkyCity
-- Create by chendh
-- 天空之城

-- UISkyCity继承自Layer
UISkyCity = class("UISkyCity", function()
    return cc.Layer:create();
end);

function UISkyCity.create()
    return UISkyCity.new();
end

-- 内部函数声明
local resize;
local ITEM_NAME     = "item_%d_%d";
local EVENT_NAME    = "event_%d";
local FRAME_NAME    = "frame_%d";
local EXTRA_NUM     = 1;
local SKY_TEMPLATE_NUM = 3;
local OCEAN_TEMPLATE_NUM = 8;
local MIN_RADIUS    = 5;
local MOVE_OCEAN_NUM = 3;
-- 图标宽度
local iconWidth = 66;
-- 图片高度
local iconHeight = 66;

-- 功能浮岛ID
local SCRAP_BONUS = 5000;
local SKY_CAMP    = 5001;
local SKY_SHOP    = 5002;
local SKY_TOWER   = 5003;

-- 默认使用阵营2 "奥鲁维之刃"
local INIT_CAMP   = 2;

-- 天空之城合图资源路径
local SKY_CITY_RES_PATH     = "images/ui/sky_city.plist";
-- 浮岛合图路径
local SKY_ISLAND_PLIST_PATH = "images/ui/sky_island_template.plist";
-- 神庙合图路径
local SKY_TEMPLE_PLIST_PATH = "images/ui/sky_city_islands_temple.plist";
-- 试炼塔合图路径
local SKY_TOWER_PLIST_PATH = "images/ui/sky_city_tower.plist";
-- 天空之城背景资源
local SKY_CITY_BACKGROUND_PATH = "images/ui/sky_city_background.plist";

-- 构造函数
function UISkyCity:ctor()
    self:setName("UISkyCity");

    -- 缓存合图
    cc.SpriteFrameCache:getInstance():addSpriteFrames(SKY_ISLAND_PLIST_PATH);
    cc.SpriteFrameCache:getInstance():addSpriteFrames(SKY_TOWER_PLIST_PATH);
    cc.SpriteFrameCache:getInstance():addSpriteFrames(SKY_TEMPLE_PLIST_PATH);

    -- 初始化
    local node = cc.CSLoader:createNode("layout/sky_city/SkyCity.csb");
    self:addChild(node);
    self.node = node;
    local screen = findChildByName(self.node, "Screen/screen");
    self.screen = screen;
    -- 设置屏幕的回弹距离为2个格子的大小
    self.screen:setBounceBoundaryX(iconWidth);
    self.screen:setBounceBoundaryY(iconHeight);

    self.lastShowXY = { ["x"] = 0, ["y"] = 0, };

    -- 适配
    resize(self);

    -- 当前处于冷却状态的位置列表
    self.coolPosList = {};

    self:initData();

    --self.screen.rows = {};
    self.screen:removeAllChildren();
    self.eventIconList = {};

    -- 初始化选中项
    self.selectPos = 0;

    self:initGrid();

    self:redraw();

    self:registerEventCallback();

    -- 注册点击事件
    self:registerTouchEvent();

    self.flag = false;

    -- 启动定时器检测是否有需要刷新的内容
    self.schedulerId = self:checkUpdate();

    ME.user.dbase:deleteTemp("explore_need_guide");
end

-- 点击响应
function UISkyCity:registerTouchEvent()
    -- 注册返回按钮点击事件
    local btnBack = findChildByName(self.node, "BT/btn_back");
    local function onBackClick(sender, eventType)
        if eventType == ccui.TouchEventType.ended then
            -- 关闭当前界面
            AudioM.playFx("ship_return");
            UIMgr.getCurrentScene():removeFormByName("UISkyCity");

            -- 加载主界面资源
            if DungeonM.dungeonType == DUNGEON_TYPE_SKY_INSTANCE or
                DungeonM.dungeonType == DUNGEON_TYPE_SKY_ACTIVITY_INSTANCE then
                UIMgr.getCurrentScene().mainMap:show();
                UIMgr.getCurrentScene().mainMap:reloadImages();
                DungeonM.dungeonType = DUNGEON_TYPE_NORMAL;
            end
        end
    end
    AddBackEventHandler(self, btnBack, onBackClick);

    -- 注册 编组 按钮点击事件
    local btnTeam = findChildByName(self.node, "BT/btn_team");
    local function onTeamClick(sender, eventType)
        if eventType == ccui.TouchEventType.ended then
            AudioM.playFx("ship_click");
            -- 打开分组界面
            require "game/ui/form/sky_city/UISkyCityGroup";
            local UISkyCityGroup = UISkyCityGroup.create();
            UIMgr.getCurrentScene():addForm(UISkyCityGroup);
        end
    end
    btnTeam:addTouchEventListener(onTeamClick);

    -- 注册 飞艇 按钮点击事件
    local btnShip = findChildByName(self.node, "BT/btn_ship");
    local function onShipClick(sender, eventType)
        if eventType == ccui.TouchEventType.ended then
            AudioM.playFx("ship_click");
            -- 根据配置打开飞艇选择界面
            require "game/ui/form/sky_city/UINewAirShipList"
            local UINewAirShipList = UINewAirShipList.create();
            UIMgr.getCurrentScene():addForm(UINewAirShipList);
        end
    end
    btnShip:addTouchEventListener(onShipClick);

    -- 注册 秘宝 按钮点击事件
    local btnSkill = findChildByName(self.node, "BT/btn_skill");
    local function onSkillClick(sender, eventType)
        if eventType == ccui.TouchEventType.ended then
            AudioM.playFx("ship_click");
            -- 打开秘宝界面
            require "game/ui/form/sky_city/UISkyCityShipSkill";
            UIMgr.getCurrentScene():removeFormByName("UISkyCityShipSkill");
            local uiSkyCityShipSkill = UISkyCityShipSkill.create();
            UIMgr.getCurrentScene():addForm(uiSkyCityShipSkill);
        end
    end
    btnSkill:addTouchEventListener(onSkillClick);

    -- 注册 仓库 按钮点击事件
    local btnStore = findChildByName(self.node, "BT/btn_store");
    local function onStoreClick(sender, eventType)
        if eventType == ccui.TouchEventType.ended then
            AudioM.playFx("ship_click");

            -- 打开仓库界面
            require "game/ui/form/sky_city/UISkyResource";
            UIMgr.getCurrentScene():removeFormByName("UISkyResource");
            local uiForm = UISkyResource.create();
            UIMgr.getCurrentScene():addForm(uiForm);
        end
    end
    btnStore:addTouchEventListener(onStoreClick);

    -- 注册 修理 按钮点击事件
    local btnRepair = findChildByName(self.node, "BT/btn_repair");
    local function onRepairClick(sender, eventType)
        if eventType == ccui.TouchEventType.ended then
            AudioM.playFx("ship_click");
            -- 如果还未开放此进度
            if AIRSHIP_GUIDE_LIMIT and AIRSHIP_GUIDE_LIMIT <= 4 then
                confirm2(CONFIRM_TYPE_OK, getLocStr("slime_note_title"), getLocStr("sky_city_unopen_tip"));
                return;
            end

            -- 进行修理
            require "game/ui/form/sky_city/UIAirShipFix";
            local function okFunc()
                if #ME.user:getAllPetIds() < NewAirShipM.repairPetNumLimit() then
                    alert(getLocStr("slime_not_enough"));
                elseif ME.user.dbase:query("money") < NewAirShipM.repairCostMoney() then
                    alert(getLocStr("money_not_enough"));
                else
                    -- 玩家点了"修理"按钮，开始播放修理光效
                    self:playRadarStartEffect();
                end
            end

            local title = NewAirShipM.query(1, "name");
            local desc = getLocStr("fix_airship_desc");
            local icon = getAirShipImgPath(NewAirShipM.query(1, "icon"));
            local okText = getLocStr("btn_text_repair");
            local okBtnEnable = true;
            local extraPara = {["costDesc"] = string.format(getLocStr("sky_city_repair_desc"),
                                        NewAirShipM.repairPetNumLimit(), NewAirShipM.repairCostMoney()),
                ["okBtnEnable"] = okBtnEnable, ["scale"] = 0.35, ["flippedX"] = true, ["offset"] = {0, 0}};

            -- 飞艇维修界面
            local uiAirShipFix = UIAirShipFix.create(title, desc, icon, okText, okFunc, extraPara);
            UIMgr.getCurrentScene():addForm(uiAirShipFix);
        end
    end
    btnRepair:addTouchEventListener(onRepairClick);

    -- 注册 补充能量 按钮点击事件
    local btnAddEnergy = findChildByName(self.node, "Screen/energy_node/btn_add_energy");
    local function onAddEnergyClick(sender, eventType)
        if eventType == ccui.TouchEventType.ended then
            AudioM.playFx("common_button");
            -- 必须天空之城指引结束
            if not NewAirShipM.isFinishGuide() then
                -- 提示
                alert(getLocStr("ship_donot_response"));
                return;
            end

            local energyInfo = SkyResourceM.queryByAlias("energy");
            if not energyInfo then
                return;
            end

            -- "没有可用的电池包！"
            if ItemM.getAmount(ME.user, energyInfo.class_id) <= 0 then
                -- 打开购买界面
                openStoreItem(energyInfo.class_id);
                return;
            end

            -- 打开使用电池包界面
            addExploreEnergy();
        end
    end
    btnAddEnergy:addTouchEventListener(onAddEnergyClick);

    -- 注册 重置雷达 按钮点击事件
    local btnReset = findChildByName(self.node, "Screen/reset_node/btn_reset");
    local function onResetClick(sender, eventType)
        if eventType == ccui.TouchEventType.ended then
            AudioM.playFx("common_button");
            -- 必须天空之城指引结束
            if not NewAirShipM.isFinishGuide() then
                -- 提示
                alert(getLocStr("ship_donot_response"));
                return;
            end

            -- 重置雷达
            -- 打开雷达重置界面
            require "game/ui/form/sky_city/UIRadarReset";
            UIMgr.getCurrentScene():removeFormByName("UIRadarReset");
            local uiForm = UIRadarReset.create();
            UIMgr.getCurrentScene():addForm(uiForm);
            AudioM.playFx("ui_open1");
        end
    end
    btnReset:addTouchEventListener(onResetClick);

    -- 注册 探索 按钮点击事件
    local btnSelect = findChildByName(self.node, "Screen/select_node/btn_select");
    local function onSelectClick(sender, eventType)
        if eventType == ccui.TouchEventType.ended then
            AudioM.playFx("ship_click");
            -- 必须天空之城指引结束
            if not NewAirShipM.isFinishGuide() then
                -- 提示
                alert(getLocStr("ship_donot_response"));

                -- 修理按钮放大一下
                local btnRepair = findChildByName(self.node, "BT/btn_repair");
                local button = ccui.ImageView:create();
                local icon = ccui.ImageView:create();
                button:loadTexture(getSkyCityPath("button"), 1);
                icon:loadTexture(getSkyCityIconPath("repair"), 1);
                button:setPosition(cc.p(43, 43));
                icon:setPosition(cc.p(43, 43));

                btnRepair:addChild(button);
                btnRepair:addChild(icon);

                local scaleTo1 = cc.ScaleTo:create(0.5, 1.5);
                local scaleTo2 = cc.ScaleTo:create(0.5, 1.5);
                local fadeOut1 = cc.FadeOut:create(0.5);
                local fadeOut2 = cc.FadeOut:create(0.5);
                local callFunc1 = cc.CallFunc:create(function ()
                    button:removeFromParent();
                end)
                local callFunc2 = cc.CallFunc:create(function ()
                    icon:removeFromParent();
                end)
                button:runAction(cc.Sequence:create(cc.Spawn:create(scaleTo1, fadeOut1), callFunc1));
                icon:runAction(cc.Sequence:create(cc.Spawn:create(scaleTo2, fadeOut2), callFunc2));
                return;
            end

            local ret = SkyExploreM.canExplore();
            if ret == true then
                -- 进行探索
                SkyExploreM.doExplore();

                -- 播放探索特效
                self:playSelectEffect();

                -- 设置探索按钮不可用
                self:setEventBtnDisable(true);
            elseif ret == "max_explore" then
                alert(getLocStr("airship_select_max_explore"));
            elseif ret == "no_energy" then
                -- alert(getLocStr("airship_select_no_energy"));
                -- 打开使用电池包界面
                showEnergyLackConfirm();
            else
                alert(getLocStr(ret));
            end
        end
    end
    btnSelect:addTouchEventListener(onSelectClick);

    local btnInfo = findChildByName(airShipBtn, "info/btn_info");

    -- 注册 "袭击/探索" 按钮点击事件
    local btnDoEvent = findChildByName(self.node, "CT/airship_btn/do_event/btn_do_event");
    local function onDoEventClick(sender, eventType)
        if eventType == ccui.TouchEventType.ended then
            -- 执行袭击或者探索
            AudioM.playFx("explore_button");
            self:doEvent();
        end
    end
    btnDoEvent:addTouchEventListener(onDoEventClick);

    -- 注册 "详细" 按钮点击事件
    local btnInfo = findChildByName(self.node, "CT/airship_btn/info/btn_info");
    local function onInfoClick(sender, eventType)
        if eventType == ccui.TouchEventType.ended then
            -- 打开浮岛详细信息界面
            AudioM.playFx("explore_button");
            self:doInfo();
        end
    end
    btnInfo:addTouchEventListener(onInfoClick);

    -- 点击雷达信息
    local radarFrame = findChildByName(self.node, "Screen/radar_info/frame");
    local function onClickRadar(sender, eventType)
        -- 点击时的回调
        if eventType == ccui.TouchEventType.ended then
            AudioM.playFx("select_radar");
            -- 查找下一个雷达节点
            local posList = table.keys(self.eventsList);
            local index = table.indexOf(posList, self.selectPos);

            if #posList <= 0 then
                return;
            end

            if index > 0 then
                index = index + 1;
            end
            if index <= 0 or index > #posList then
                index = 1;
            end

            -- 排除功能浮岛的选择
            local initIndex = index;
            repeat
                local eventInfo = self.eventsList[posList[index]];
                local rule = SkyExploreM.query(eventInfo["id"], "rule");
                if rule == "usage" or rule == "sky_activity" then
                    index = index + 1;

                    if index <= 0 or index > #posList then
                        index = 1;
                    end

                    if index == initIndex then
                        return;
                    end
                else
                    break;
                end

            until false;

            -- 选中下一个目标
            self:onScreenItemClick(posList[index]);
        end
    end
    radarFrame:addTouchEventListener(onClickRadar);

    -- 点击飞艇或者浮岛
    local airShip = findChildByName(self.node, "CT/airship_panel");
    local function onClickAirShip(sender, eventType)
        -- 点击时的回调
        if eventType == ccui.TouchEventType.ended then
            AudioM.playFx("button_click");

            local infoNode = findChildByName(self.node, "CT/airship_btn/info");
            if infoNode:isVisible() then
                -- 详细
                AudioM.playFx("ui_open1");
                self:doInfo();
            else
                -- 探索/袭击
                AudioM.playFx("ui_open1");
                self:doEvent();
            end
        end
    end
    airShip:addTouchEventListener(onClickAirShip);
end

-- 事件响应
function UISkyCity:registerEventCallback()
    EventMgr.removeAll("UISkyCity");

    -- 注册获得焦点的回调
    EventMgr.register("UISkyCity", event.FOCUS_CHANGED, function(para)
        if para["get"] == "UISkyCity" then
            if not NewAirShipM.isFinishGuide() then
                UIMgr:getCurrentScene().topMenu:show(TOPMENU_FRAME_MONEY, TOPMENU_FRAME_GEM);
            else
                UIMgr:getCurrentScene().topMenu:show(TOPMENU_FRAME_AIRSHIP_SCORE, TOPMENU_FRAME_AIRSHIP_ATTRIB, TOPMENU_FRAME_HEAD_AIRSHIP);
            end

            -- 播放背景音乐
            AudioM.playBgMusic("bgm_sky");

            -- 尝试触发指引
            self:tryToGuide();

            -- 重绘下焦点图标和雷达信息
            self:redrawFocus();
            self:redrawRadarInfo();

            -- 自动滚动屏幕到有目标的区域
            self:autoScroll();

            -- 检测是否要播放探索按钮提示光效
            self:checkSelectBtnTipEffect();

            -- 检查是否要播放编组未满的效果
            self:checkTeamBtnTipEffect();

            -- 检查是否要播放有飞艇可以升阶的提示
            self:checkShipUpRankEffect();

            -- 如果切换回来的时候记录了神殿id，那么说明刚刚去探索了资源浮岛，并且发现了神殿
            -- 播放发现神殿的光效
            if self.treasureId or self.triggerId then
                local triggerId = self.triggerId;

                if self.triggerId then
                    -- 触发了特殊事件，要弹个提示
                    local name = SkyExploreTriggerM.query(self.triggerId, "name");

                    -- 飞艇残骸的话因为有播放飞艇爆炸效果，这里不弹出不提示
                    if SkyExploreTriggerM.query(self.triggerId, "rule") ~= "ship_remains" then
                        alert(string.format(getLocStr("find_explore_trigger"), name));
                    end
                end

                self.treasureId = nil;
                self.triggerId = nil;

                -- 获取神殿/飞艇目标节点
                local airShip = findChildByName(self.node, "CT/air_ship");

                -- 先隐藏了准心，目标信息，按钮节点。播放完光效后显示
                -- 准心节点
                local reticleNode = findChildByName(self.node, "CT/reticle_node");
                reticleNode:setVisible(false);
                -- 飞船信息节点
                local airShipInfoNode = findChildByName(self.node, "CT/airship_info");
                airShipInfoNode:setVisible(false);
                -- 飞船按钮节点
                local airShipBtn = findChildByName(self.node, "CT/airship_btn");
                airShipBtn:setVisible(false);
                local btnDoEvent = findChildByName(airShipBtn, "do_event/btn_do_event");
                local btnInfo = findChildByName(airShipBtn, "info/btn_info");

                if self.talkTemplate == "temple_guide" then
                    local function callback()
                        self.talkTemplate = nil;
                        self:playReticleEffect();
                        airShipInfoNode:setVisible(true);
                        airShipBtn:setVisible(true);
                        self.flag = false;

                        -- 播放下目标浮岛/飞艇出现的特效
                        self:playAirshipBtnEffect(btnDoEvent);
                        self:playAirshipBtnEffect(btnInfo);
                    end
                    ME.user.dbase:deleteTemp("explore_need_guide");

                    startDialogueTemplate(self.talkTemplate, callback);
                elseif SkyExploreTriggerM.query(triggerId, "rule") == "ship_remains" then
                    -- 飞艇残骸出现的特效
                    self:playShipRemainsAppear();
                else
                    local function frameEventFunc(frame)
                        if frame.getEvent and frame:getEvent() == "pilor" then
                            local index = frame:getFrameIndex();

                            -- 光效播放完了，显示准心，目标信息，按钮节点
                            if index == 73 then
                                self:playReticleEffect();
                                airShipInfoNode:setVisible(true);
                                airShipBtn:setVisible(true);

                                -- 播放下目标浮岛/飞艇出现的特效
                                self:playAirshipBtnEffect(btnDoEvent);
                                self:playAirshipBtnEffect(btnInfo);
                            end
                        end
                    end
                    -- 播放神殿前面灌木丛飞散的效果
                    playEffect(airShip, 1812, 0, 0, frameEventFunc);
                end
            end

            -- 如果有战斗奖励效果还没播放，则播放一下
            if type(self.combatBonus) == "table" and #self.combatBonus > 0 then
                self:playSkyCityBonusEffect(self.combatBonus);
                self.combatBonus = nil;
            end

            self:playIslandDisappear();

            -- 如果有获得焦点的回调还没处理，那么调用一下
            if self.focusChangedCallback then
                self.focusChangedCallback();
                self.focusChangedCallback = nil;
            end

            -- 检查下屏幕图标的效果是否需要变化
            self:checkIconEffect();
        elseif para["lose"] == "UISkyCity" then

        end
    end);

    -- 探索事件
    EventMgr.register("UISkyCity", event.SKY_CITY_EXPLORE, function(para)
        self.exploreEnd = true;
        self.newData = para;

        -- 如果需要刷新界面
        if para["isRefresh"] == 1 then
            self:doExploreResult();
        end
    end);

    -- 触发引导
    EventMgr.register("UISkyCity", event.TRIGGER_ONCE_GUIDE, function(para)
        self.talkTemplate = para.talkTemplate;

        if self.talkTemplate ~= "temple_guide" then
            wait(20);
        end

        -- 如果获得了奖励，需要记录下来，以便后续播放飞入仓库的动画
        local bonus = para.bonus;
        if type(bonus) == "table" and #bonus > 0 then
            self.guideBonus = bonus;
        end
    end);

    -- 重置雷达
    EventMgr.register("UISkyCity", event.SKY_CITY_RESET_EXPLORE, function(para)
        self:initData();
        self.screen:removeAllChildren();
        self.eventIconList = {};

        -- 初始化选中项
        self.selectPos = 0;

        self:initGrid();

        self:redraw();

        self.screen:jumpToPercentBothDirection(cc.p(50, 50));

        -- 播放雷达重置特效
        self:playRadarResetEffect();

        -- 刷新下冷却时间
        self:redrawCoolTime();
    end);

    -- 天空之城事件结果
    EventMgr.register("UISkyCity", event.SKY_CITY_EXPLORE_RESULT, function(para)
        local oldData = self.eventsList[para["pos"]];

        self.exploreRecord = oldData;

        self:initData();

        local isOld = false;

        -- 旧数据被抹除
        if para.result["result"] == 1 and self.eventsList[para["pos"]] == nil and
            self.exploreRecord then
            isOld = true;
            self.selectPos = para["pos"];

            self.exploreRecord["disappear"] = true;
        else
            self.exploreRecord = nil;
        end

        -- 更新下浮岛节点
        self:updateEvents(para["pos"]);

        self:redrawAirShip(nil, isOld);
        self:redrawFocus();

        -- 检测是否要播放探索按钮提示光效
        self:checkSelectBtnTipEffect();

        -- 关闭 "加载中" 界面
        closeCommunicatingForm();

        local result = para.result;
        if result.attacker and result.defenser and result.record then

        end

        -- 神殿解除封印获得奖励
        if oldData["treasure_id"] or oldData["trigger_id"] then
            -- 会都到这里的应该就只有神殿的奖励了
            local templeId = oldData["treasure_id"];
            local title = getLocStr("sky_city");
            local msg;

            if templeId then
                title = SkyResourceM.querySkyTreasure(templeId, "name");
                msg = getLocStr("treasure_bonus_msg");
            end

            -- 奖励提示框关闭回调
            local function callback()
                -- 播放奖励特效
                self:playSkyCityBonusEffect(result["bonus"]);
            end

            showBonusNotifyForm(title, result["bonus"], nil, msg, nil, nil, nil, callback);

            -- 播放浮岛消失效果
            self:playIslandDisappear();
        end

        if result.result == "find_treasure" then
            -- 探索资源浮岛的时候发现了神殿
            self.treasureId = result.treasure_id;
            EventMgr.fire(event.FIND_TREASURE_TEMPLE, {["treasure_id"] = self.treasureId, ["rule"] = "treasure"});
        elseif result.trigger_id then
            -- 触发了特殊事件
            self.triggerId = result.trigger_id;
        end
    end);

    -- 天空之城触发事件选择选项结果
    EventMgr.register("UISkyCity", event.EXPLORE_CHOICE_OPTION_RESULT, function(para)
        -- 执行选择选项结果处理
        self:doChoiceOptionResult(para);
    end);

    -- 战斗开始回调
    EventMgr.register("UISkyCity", event.START_SKY_BATTLE, function(para)
        require "game/ui/form/sky_city/UISkyCityCombat"

        local UISkyCityCombat = UISkyCityCombat.create(para.battle);
        UIMgr.getCurrentScene():addForm(UISkyCityCombat);

        self.combatBonus = para.battle.bonus;
    end);

    -- 关注电池包批量使用
    EventMgr.register("UISkyCity", event.ITEM_BATCH_USE, function(para)
        local energyInfo = SkyResourceM.queryByAlias("energy");
        if para["class_id"] == energyInfo.class_id then
            self:redrawEnergy();
        end
    end);

    -- 关注玩家属性变化
    EventMgr.register("UISkyCity", event.USER_INFO_UPDATED, function(para)
        if para == "ship_energy" or para == "active_airship" or para == "apply_prop" then
            self:redrawEnergy();
        elseif para == "airship_guide" or para == "sky_explore" then
            if NewAirShipM.isFinishGuide() then
                self:initData();
                self:initGrid();
                self:redraw();
            end

            if not NewAirShipM.isFinishGuide() then
                UIMgr:getCurrentScene().topMenu:show(TOPMENU_FRAME_MONEY, TOPMENU_FRAME_GEM);
            else
                UIMgr:getCurrentScene().topMenu:show(TOPMENU_FRAME_AIRSHIP_SCORE, TOPMENU_FRAME_AIRSHIP_ATTRIB, TOPMENU_FRAME_HEAD_AIRSHIP);
            end
        end
    end);

    -- 引导等待
    EventMgr.register("UISkyCity", event.SC_EXPLORE_WAIT_GUIDE, function(para)
        local function deleteWaitGuide()
            ME.user.dbase:deleteTemp("explore_need_guide");
            local severTimes = ME.user.dbase:query("sky_explore_times", 0);
            ME.user.dbase:setTemp("client_times", severTimes);
        end
        performWithDelay(self.node, deleteWaitGuide, 5);
    end);

    -- 天空之城编组出战
    EventMgr.register("UISkyCity", event.SKY_ACTIVE_GROUP, function(para)
        -- 有些飞艇信息与玩家出战的阵营有关，所以当玩家更换出战阵营时，需要刷新下飞艇信息
        local camp = ME.user.dbase:query("activedSkyGroup");
        local eventInfo = self.eventsList[self.selectPos];
        if eventInfo then
            if eventInfo["id"] == SKY_TOWER then
                -- 如果当前选择的目标是试练塔，那么需要重绘下海面
                local oceanNode = findChildByName(self.node, "CT/background/ocean_node");
                oceanNode:removeAllChildren();
                local node;

                node = cc.CSLoader:createNode(string.format("layout/sky_city/background/background_ocean_%d.csb", 1));

                node.id = camp;
                node:setName("ocean" .. 1);
                oceanNode:addChild(node);

                -- 刷新下目标的信息
                self:redrawAirShipInfo(eventInfo);

                -- 标记下需要特效
                ME.user.dbase:setTemp("skytower_apear", 1);
            end
        end
    end);

    -- 天空活动关闭了
    EventMgr.register("UISkyCity", event.ACTIVITY_CLOSE, function(para)
        local pos = para["pos"];

        -- 如果没有坐标，那么说明不是被探索出来的天空活动，直接返回
        if not pos then
            return;
        end

        self:initData();

        -- 更新下浮岛节点
        self:updateEvents(pos);

        self:redrawAirShip();

        if self.selectPos == pos then
            self.selectPos = 0;
            self:redrawFocus();
        end

    end);

    -- 界面析构后，需要清理下
    self:registerScriptHandler(function(ev)
        if ev == "exit" then
            self.screen:removeAllChildren();
            EventMgr.removeAll("UISkyCity");
            -- 主动GC掉冷却刷新列表
            self.coolPosList = nil;

            -- 关闭定时器
            if self.schedulerId then
                cc.Director:getInstance():getScheduler():unscheduleScriptEntry(self.schedulerId);
            end

            -- 还原遗迹界面隐藏
            local uiRelic  = UIMgr.getCurrentScene():getFormByName("UISkyCityRuins");
            local bgm = AudioM.queryFormAudioInfo("UIMainMap", "open_bgm");

            if uiRelic then
                uiRelic:setVisible(true);

                -- 处理下界面切换的背景音乐
                bgm = AudioM.queryFormAudioInfo("UISkyCityRuins", "open_bgm");
                if bgm ~= nil and bgm ~= "" then
                    AudioM.playBgMusic(bgm);
                end

                -- 不需要处理主地图显示
                return;
            end
            -- 显示主地图
            if UIMgr.getCurrentScene().mainMap then
                UIMgr.getCurrentScene().mainMap:show();
                if bgm ~= nil and bgm ~= "" then
                    AudioM.playBgMusic(bgm);
                end
            end
            -- 刷新下底部菜单的显示
            local uiBottom = UIMgr.getCurrentScene():getFormByName("UIBottomMenu");
            if uiBottom then
                uiBottom:updateAirShipBubble();
            end

            UIMgr.tryClearUnusedTextures();
        elseif ev == "enter" then
            -- 这里把主地图隐藏清楚，降低GL calls
            if UIMgr.getCurrentScene().mainMap then
                UIMgr.getCurrentScene().mainMap:hide();
            end
            -- 把遗迹界面隐藏
            local uiRelic  = UIMgr.getCurrentScene():getFormByName("UISkyCityRuins");
            if uiRelic then
                uiRelic:setVisible(false);
            end
        elseif ev == "enterTransitionFinish" then
            -- 屏幕跳到中间
            self.screen:jumpToPercentBothDirection(cc.p(50, 50));
        end
    end)
end

-- 探索/袭击
function UISkyCity:doEvent(pos, extraData)
    local targetPos = pos or self.selectPos;
    local eventInfo = self.eventsList[targetPos];

    -- 如果获取事件失败，那么刷新一下数据和浮岛节点
    if not eventInfo then
        self:initData();

        -- 更新下浮岛节点
        self:updateEvents(targetPos);

        self:redrawAirShip();
        self:redrawFocus();
        return;
    end

    if eventInfo.treasure_id then
        -- 是宝藏事件
        local treasureId = {
            ["treasure_id"] = eventInfo.treasure_id,
            ["explore_index"] = eventInfo.index,
        };
        self:findTreasureTip(treasureId);
        return;
    elseif eventInfo.trigger_id then
        -- 触发事件

        local function openTriggerInfo()
            -- 弹出触发事件界面
            require "game/ui/form/sky_city/UIExploreTriggerInfo";
            local uiDialog = UIExploreTriggerInfo.create(eventInfo["index"]);
            UIMgr.getCurrentScene():addForm(uiDialog);
        end

        if SkyExploreTriggerM.query(eventInfo.trigger_id, "rule") == "ship_remains" and
            eventInfo.ship_id and
            NewAirShipM.hasOwnShip(eventInfo.ship_id) then
            -- 如果是飞艇残骸，并且玩家出战的飞艇与残骸一直，直接去调查（第一选项）
            local options = SkyExploreTriggerM.query(eventInfo.trigger_id, "options");
            local optionId = options[#options];
            SkyExploreTriggerM.choiceOption(eventInfo["index"], optionId, {});
        else
            if SkyExploreTriggerM.query(eventInfo.trigger_id, "rule") == "ship_remains" then
                -- 屏幕震动一下
                playShakeEffect(self, 0.03, 4, 4);

                -- 延迟一下再打开事件界面
                performWithDelay(self, openTriggerInfo, 0.2);
                return;
            end

            -- 打开事件界面
            openTriggerInfo();
        end

        return;
    end

    if SkyExploreM.query(eventInfo.id, "rule") == "usage" then
        self:openUsageIsland(eventInfo.id);
        return;
    elseif SkyExploreM.query(eventInfo.id, "rule") == "sky_activity" then
        -- 如果是天空活动浮岛，则打开天空活动界面
        self:openSkyActivityInfo(eventInfo["activity_id"]);
        return;
    end

    local eventConfig = SkyExploreM.query(eventInfo.id);

    -- 如果是天空副本
    if eventConfig["rule"] == "instance" then
        -- 当前正在迷宫中
        if DungeonM.isInDungeon() then
            alert(getLocStr("pausing_forbid"));
            return;
        end

        -- 显示关卡进入界面
        require "game/ui/form/dungeon/UIEnterDungeon";
        local dbase = eventConfig["dbase"];
        local dungeonId = dbase["dungeon_id"];
        if type(dungeonId) ~= "number" then
            error(string.format("未正确配置dungeon_id，请检查！(event_id=%d)", eventInfo.id));
        end
        local uiForm = UIEnterDungeon.create(dungeonId);
        UIMgr.getCurrentScene():removeFormByName("UIEnterDungeon");
        UIMgr.getCurrentScene():addForm(uiForm);
        AudioM.playFx("ui_open1");
        return;
    end

    -- 如果是时空裂痕，判断下当前出战阵营是否与时空裂痕对应
    local activeCamp = ME.user.dbase:query("activedSkyGroup");

    if eventConfig["rule"] == "whirlpool" and activeCamp ~= eventInfo["camp"] then
        local airShipBtn = findChildByName(self.node, "CT/airship_btn");

        -- 绘制按钮
        local btnDoEvent = findChildByName(airShipBtn, "do_event/btn_do_event");
        btnDoEvent:setTouchEnabled(false);
        local function openConfirm()
            -- 获得当前时空裂痕的标题
            local needCamp = eventInfo["camp"];
            local name = string.format(getLocStr("whirlpool_title"), CampM.query(needCamp, "whirlpool_color"));
            local campName = CampM.query(needCamp, "name");
            local msg = string.format(getLocStr("cant_access_whirlpool"), campName);
            local extraPara = {["line_word_count"] = 19};
            confirm2(CONFIRM_TYPE_OK, name, msg, nil, nil, nil, nil, extraPara);
            btnDoEvent:setTouchEnabled(true);
        end
        -- 屏幕震动一下
        playShakeEffect(self, 0.03, 4, 4);

        -- 延迟一下再打开事件界面
        performWithDelay(self, openConfirm, 0.2);
        return;
    end

    -- 弹出提示框(战斗需要消耗)
    local cost = SkyExploreM.calcExploreCost(eventInfo.index);
    if cost then
        self:startFightTip(eventInfo, extraData);
    else
        SkyExploreM.doEvent(eventInfo.index, extraData);

        if eventConfig["rule"] ~= "resource" then
            -- 显示 "加载中" 界面
            showCommunicatingForm(getLocStr("loading"));
        else
            require "game/ui/form/sky_city/UIAirShipExplore";
            local uiDialog = UIAirShipExplore.create(eventInfo);
            UIMgr.getCurrentScene():addForm(uiDialog);
        end
    end
end

-- 详细
function UISkyCity:doInfo()
    local eventInfo = nil;
    if self.selectPos then
        eventInfo = self.eventsList[self.selectPos];
    end

    -- 如果获取事件失败，那么刷新一下数据和浮岛节点
    if not eventInfo then
        self:initData();

        -- 更新下浮岛节点
        self:updateEvents(self.selectPos);

        self:redrawAirShip();
        self:redrawFocus();
        return;
    end

    if eventInfo and SkyExploreM.query(eventInfo.id, "rule") == "usage" then
        if eventInfo.id == SKY_CAMP then
            require "game/ui/form/market/UIScrapMarket"
            UIMgr.getCurrentScene():removeFormByName("UIScrapMarket");
            local uiScrap = UIScrapMarket.create("SkyPetShop");
            UIMgr.getCurrentScene():addForm(uiScrap);
        elseif eventInfo.id == SKY_TOWER then
            -- 打开试练塔奖励界面
            AudioM.playFx("ship_click");

            require "game/ui/form/sky_city/UISkyCityTowerBonus";
            local camp = ME.user.dbase:query("activedSkyGroup");
            local uiDialog = UISkyCityTowerBonus.create(camp);
            UIMgr.getCurrentScene():addForm(uiDialog);
        end

        return;
    end

    if eventInfo then
        local rule = SkyExploreM.query(eventInfo["id"], "rule");
        if rule == "whirlpool" then
            require "game/ui/form/sky_city/UISkyWhirlpoolInfo";
            local uiForm = UISkyWhirlpoolInfo.create(eventInfo);
            UIMgr.getCurrentScene():addForm(uiForm);
        else
            require "game/ui/form/sky_city/UISkyCityIslandInfo";
            local uiSkyCityIslandInfo = UISkyCityIslandInfo.create(eventInfo);
            UIMgr.getCurrentScene():addForm(uiSkyCityIslandInfo);
        end
    end
end

-- 获得挖掘机
function UISkyCity:showFeatureItem()
    local machineFeatureId = FormulaM.invoke("CALC_MACHINE_FEATURE_ID");
    -- 弹出提示
    local uiForm = UIFeatureDesc.create(machineFeatureId, 0);
    UIMgr.getCurrentScene():addForm(uiForm);
end

-- 发起战斗提示
function UISkyCity:startFightTip(eventInfo, extraData)
    local config = SkyExploreM.query(eventInfo.id);
    local index = eventInfo.index;
    local name = config["name"];

    local function okFun()
        -- 事件
        local ret = SkyExploreM.doEvent(index, extraData);

        if type(ret) == "string" then
            alert(getLocStr(ret));
        elseif ret == true then
            -- 显示 "加载中" 界面
            showCommunicatingForm(getLocStr("loading"));
        end
    end

    local msg = "";
    local cost = SkyExploreM.calcExploreCost(eventInfo.index);
    local okBtnEnable = true;
    for classId, count in pairs(cost) do
        if type(classId) == "string" then
            if ME.user:queryAttrib(classId) < count then
                okBtnEnable = false;
                msg = spliceLocStr(msg, "[red]" .. count .. "[-]", FieldsM.getFieldName(classId));
            else
                msg = spliceLocStr(msg, "[blue]" .. count .. "[-]", FieldsM.getFieldName(classId));
            end
        else
            if Item.getAmount(ME.user, classId) < count then
                okBtnEnable = false;
                msg = spliceLocStr(msg, "[red]" .. count .. "[-]", ItemM.query(classId, "unit"), ItemM.query(classId, "name"));
            else
                msg = spliceLocStr(msg, "[blue]" .. count .. "[-]", ItemM.query(classId, "unit"), ItemM.query(classId, "name"));
            end
        end
    end

    -- BOSS/怪物/玩家
    if table.indexOf({"boss", "monster", "player", "whirlpool"},
        config["rule"]) ~= -1 then
        -- 特殊提示框
        local name = config["name"];
        msg = string.format(getLocStr("continue_attack"), msg);
        local iconPath = nil;

        if config["rule"] == "player" then
            -- 国旗
            local nationId = eventInfo["enemy"].nation or 0;
            local icon = TopM.queryNationality(nationId, "icon");
            iconPath = getNationalityIconPath(icon);
            -- 玩家名称
            name = eventInfo["enemy"].name;
        end

        name = getNickName(name);
        confirm2(CONFIRM_TYPE_OK_CANCEL, name, msg, okFun, nil, getLocStr("btn_text_ok"), getLocStr("btn_text_cancel"),
            {["okBtnEnable"] = okBtnEnable, ["cancelColor"] = "blue", ["titleIcon"] = iconPath, ["skyExploreTime"] = eventInfo});
        return;
    end

    msg = string.format(getLocStr("sky_strike_cost"), msg);

    -- 弹出提示框
    confirm2(CONFIRM_TYPE_OK_CANCEL, name, msg, okFun, nil,
        getLocStr("btn_text_attack"), getLocStr("btn_text_cancel"), {["okBtnEnable"] = okBtnEnable, ["cancelColor"] = "blue"});
end

-- 获得宝藏提示
function UISkyCity:findTreasureTip(treasureInfo)
    local treasureId = treasureInfo.treasure_id;
    local index = treasureInfo.explore_index;

    -- 获取宝藏信息
    local name = SkyResourceM.querySkyTreasure(treasureId, "name");
    local classId = SkyResourceM.querySkyTreasure(treasureId, "class_id");
    local costId = SkyResourceM.querySkyTreasure(treasureId, "key");
    local costName = ItemM.query(costId, "name");
    local okBtnEnable = true;

    local function okFun()
        local function frameEventFunc(frame)
            if frame.getEvent and frame:getEvent() == "pilor" then
                local fIndex = frame:getFrameIndex();

                if fIndex == 15 then
                    -- 领取宝藏
                    SkyExploreM.doEvent(index);
                end
            end
        end

        -- 播放光效
        local islandImg = findChildByName(self.node, "CT/air_ship");
        playEffect(islandImg, 1814, 0, 0, frameEventFunc);
    end
    local msg;
    if type(costId) == "number" then
        msg = string.format(getLocStr("find_treasure"), costName, ItemM.getAmount(ME.user, costId));
        if ItemM.getAmount(ME.user, costId) <= 0 then
            -- 没有钥匙，按钮置灰
            okBtnEnable = false;
        end
    else
        -- 没有消耗的
        okFun();
        return;
    end

    -- 弹出提示框
    confirm2(CONFIRM_TYPE_OK_CANCEL, name, msg, okFun, nil,
        getLocStr("btn_text_open"), getLocStr("btn_text_cancel"), {["okBtnEnable"] = okBtnEnable, ["cancelColor"] = "blue"});
end

-- 检测是否要播放探索按钮提示光效
function UISkyCity:checkSelectBtnTipEffect()
    local selectNode = findChildByName(self.node, "Screen/select_node");
    local tipEffect = findChildByName(selectNode, "tip_effect");
    local tipEffect2 = findChildByName(selectNode, "tip_effect2");

    if not NewAirShipM.isFinishGuide() then
        return;
    end

    tipEffect:removeAllChildren();
    tipEffect2:removeAllChildren();
    if SkyExploreM.canExplore() == true and not self.isStopAllEffects then
        playEffect(tipEffect, 1801);
        playEffect(tipEffect2, 1817);
    end
end

-- 检查是否要播放编组未满的效果
function UISkyCity:checkTeamBtnTipEffect()
    local tipImg = findChildByName(self.node, "BT/btn_team/tip");
    tipImg:stopAllActions();

    if not NewAirShipM.isFinishGuide() then
        return;
    end

    local maxNum = SkyCombatM.getMaxPetNum();
    local armyNum = #SkyCombatM.getSkyArmy();
    local robatList = SkyRobotM.getRobotsByCamp(NewAirShipM.getActiveAirShip());
    -- 计算下出战了多少机器人
    local robatNum = 0;
    for _, num in pairs(robatList) do
        robatNum = robatNum + num;
    end

    local freeRobot = SkyRobotM.fetchRobotList();
    local campId = ME.user.dbase:query("activedSkyGroup") or INIT_CAMP;
    local freePetNum = #CampM.getCampMyPet(campId) - armyNum;

    if armyNum + robatNum < maxNum and (#freeRobot > 0 or freePetNum > 0) then
        tipImg:setVisible(true);

        -- 恢复初始比例
        if not tipImg.initScale then
            tipImg.initScale = tipImg:getScale();
        end
        tipImg:setScale(tipImg.initScale);

        playBubbleSlightAnima(tipImg, 0.05);
    else
        tipImg:setVisible(false);
    end
end

-- 检查是否要播放有飞艇可以升阶的提示
function UISkyCity:checkShipUpRankEffect()
    local tipImg = findChildByName(self.node, "BT/btn_ship/tip");
    tipImg:stopAllActions();

    -- 获取所拥有的飞艇
    local ownShips = NewAirShipM.getOwnShips() or {};
    for _, shipId in pairs(ownShips) do
        if NewAirShipM.canUpRank(shipId) then
            tipImg:setVisible(true);

            -- 恢复初始比例
            if not tipImg.initScale then
                tipImg.initScale = tipImg:getScale();
            end
            tipImg:setScale(tipImg.initScale);

            playBubbleSlightAnima(tipImg, 0.05);

            return;
        end
    end

    tipImg:setVisible(false);
end

-- 绘制界面
function UISkyCity:redraw()
    local node = self.node;
    local CT = findChildByName(node, "CT");
    local screenNode = findChildByName(node, "Screen");

    if not NewAirShipM.isFinishGuide() and NewAirShipM.getGuideProgress() < 5 then
        self:redrawBrokenShip();
        return;
    end

    -- 绘制飞船信息
    self:redrawAirShip();

    -- 屏幕背景
    local targetBg = findChildByName(screenNode, "screen_bg");

    -- 屏幕上的浮岛目标
    self.screen:setVisible(true);

    -- 绘制所有的浮岛
    self:redrawEvents();

    -- 能量相关
    self:redrawEnergy();

    -- 播放屏幕效果
    self:playScreenEffects();

    -- 重置雷达相关
    -- 重置按钮
    local resetBtn = findChildByName(screenNode, "reset_node/btn_reset");
    local kerning = -2;
    if not isChineseLang() then
        kerning = 0;
    elseif getLang() == "fr" then
        kerning = -1;
    end
    TextStyleM.setTextStyle(resetBtn, TextStyleM.TEXT_SIZE_SMALL2, TextStyleM.TEXT_COLOR_BEIGE, true, kerning);
    resetBtn:setTitleText(getLocStr("btn_text_reset_radar"));
    resetBtn:setVisible(true);

    -- 绘制冷却时间
    self:redrawCoolTime();

    -- 探索按钮
    local selectBtn = findChildByName(screenNode, "select_node/btn_select");
    selectBtn:setVisible(true);
    -- 探索进度条
    local selectBar = findChildByName(screenNode, "select_node/progress");
    selectBar:setPercent(0);

    -- 底部的按钮
    local btnTeam = findChildByName(node, "BT/btn_team");
    local btnShip = findChildByName(node, "BT/btn_ship");
    local btnSkill = findChildByName(node, "BT/btn_skill");
    local btnStore = findChildByName(node, "BT/btn_store");
    local btnRepair = findChildByName(node, "BT/btn_repair");

    btnTeam:setVisible(true);
    btnShip:setVisible(true);
    btnSkill:setVisible(true);
    btnStore:setVisible(true);
    btnRepair:setVisible(false);

    local teamEffect = findChildByName(node, "BT/team_effect");
    local shipEffect = findChildByName(node, "BT/ship_effect");
    teamEffect:stopAllActions();
    shipEffect:stopAllActions();
    teamEffect:setVisible(false);
    shipEffect:setVisible(false);

    btnTeam:stopAllActions();
    if btnTeam.effect then
        btnTeam.effect:removeFromParent();
    end
    if btnShip.effect then
        btnShip.effect:removeFromParent();
    end
end

-- 绘制损毁的飞船界面
function UISkyCity:redrawBrokenShip()
    local node = self.node;
    local CT = findChildByName(node, "CT");
    local screenNode = findChildByName(node, "Screen");

    -- 背景使用沙漠图片
    local background = findChildByName(CT, "background");
    local skyBg = findChildByName(self.node, "CT/sky_bg");
    background:setVisible(false);

    -- 飞船隐藏起来
    local airShip = findChildByName(CT, "air_ship");
    airShip:setVisible(false);

    -- 准心节点
    local reticleNode = findChildByName(CT, "reticle_node");
    reticleNode:setVisible(false);
    -- 飞船信息节点
    local airShipInfoNode = findChildByName(CT, "airship_info");
    airShipInfoNode:setVisible(false);
    -- 飞船按钮节点
    local airShipBtn = findChildByName(CT, "airship_btn");
    airShipBtn:setVisible(false);

    -- 屏幕背景
    local targetBg = findChildByName(screenNode, "screen_bg");
    -- 载入一张破损的屏幕的图片
    targetBg:loadTexture(getSkyCityPath("broken_screen"));

    -- 屏幕上的浮岛目标
    -- 直接隐藏屏幕
    self.screen:setVisible(false);

    local kerning = -2;
    if not isChineseLang() then
        kerning = 0;
    end

    -- 能量相关
    -- "充能"按钮
    local btnAddEnergy = findChildByName(screenNode, "energy_node/btn_add_energy");
    btnAddEnergy:setVisible(false);
    TextStyleM.setTextStyle(btnAddEnergy, TextStyleM.TEXT_SIZE_SMALL2, TextStyleM.TEXT_COLOR_BEIGE, true, kerning);
    btnAddEnergy:setTitleText(getLocStr("btn_text_add_energy"));

    local energyText = findChildByName(screenNode, "energy_node/energy_text");
    energyText:setVisible(false);

    -- 能量条
    local curEnergy = findChildByName(screenNode, "energy_node/cur_energy");
    curEnergy:setVisible(false);

    -- 重置雷达相关
    -- 重置按钮
    local resetBtn = findChildByName(screenNode, "reset_node/btn_reset");
    TextStyleM.setTextStyle(resetBtn, TextStyleM.TEXT_SIZE_SMALL2, TextStyleM.TEXT_COLOR_BEIGE, true, kerning);
    resetBtn:setTitleText(getLocStr("btn_text_reset_radar"));
    resetBtn:setVisible(false);

    local btnTeam = findChildByName(node, "BT/btn_team");
    local btnShip = findChildByName(node, "BT/btn_ship");
    local btnSkill = findChildByName(node, "BT/btn_skill");
    local btnStore = findChildByName(node, "BT/btn_store");
    local btnRepair = findChildByName(node, "BT/btn_repair");

    btnTeam:setVisible(false);
    btnShip:setVisible(false);
    btnSkill:setVisible(false);
    btnStore:setVisible(true);

    local teamEffect = findChildByName(node, "BT/team_effect");
    local shipEffect = findChildByName(node, "BT/ship_effect");
    teamEffect:setVisible(true);
    shipEffect:setVisible(true);

    teamEffect:stopAllActions();
    shipEffect:stopAllActions();
    local function teamBtnEffect()
        playEffect(teamEffect, 1804);

        performWithDelay(teamEffect, teamBtnEffect, math.random(5, 10));
    end

    local function shipBtnEffect()
        playEffect(shipEffect, 1804);

        performWithDelay(shipEffect, shipBtnEffect, math.random(5, 10));
    end

    performWithDelay(teamEffect, teamBtnEffect, math.random(5, 10));
    performWithDelay(shipEffect, shipBtnEffect, math.random(5, 10));

    local curProgress = NewAirShipM.getGuideProgress();
    -- 修理按钮
    if curProgress < 6 then
        btnRepair:setVisible(true);
    else
        btnRepair:setVisible(false);
    end
end

-- 绘制冷却时间
function UISkyCity:redrawCoolTime()
    -- 冷却时间
    local coolTime = findChildByName(self.node, "Screen/reset_node/cool_time");
    local timePanel = findChildByName(coolTime, "time_panel");
    local image1 = findChildByName(timePanel, "image1");
    local image2 = findChildByName(timePanel, "image2");
    local image3 = findChildByName(timePanel, "image3");
    local image4 = findChildByName(timePanel, "image4");

    coolTime:setVisible(true);
    timePanel:stopAllActions();

    -- 刷新倒计时
    local function refreshCoolTime(isInit)
        local coolTime = SkyExploreM.leftResetTime();

        coolTime = math.ceil(coolTime / 60) * 60;

        local num1 = math.floor(coolTime / 3600 / 10);
        local num2 = math.floor(coolTime / 3600) % 10;
        local num3 = math.floor(coolTime % 3600 / 60 / 10);
        local num4 = math.floor(coolTime % 3600 / 60) % 10;
        local numHeight = 32;

        local imageArr = { image1, image2, image3, image4, };
        local numArr = { num1, num2, num3, num4, };

        if isInit then
            for i = 1, #imageArr do
                imageArr[i]:setPositionY(numArr[i] * numHeight + numHeight);
                imageArr[i].lastNum = numArr[i];
            end
        else
            for i = 1, #imageArr do
                if imageArr[i].lastNum ~= numArr[i] then
                    -- 数字发生了变化，需要滚动

                    -- 如果是从0变成了本列的最大值，那么先移动下图片的Y轴坐标，变成显示最大值下面的0
                    if i == 1 and imageArr[i].lastNum == 0 and numArr[i] == 4 then
                        imageArr[i]:setPositionY(6 * numHeight);
                    elseif i == 3 and imageArr[i].lastNum == 0 and numArr[i] == 5 then
                        imageArr[i]:setPositionY(7 * numHeight);
                    elseif (i == 2 or i == 4) and imageArr[i].lastNum == 0 and numArr[i] == 9 then
                        imageArr[i]:setPositionY(11 * numHeight);
                    end

                    -- 滚动
                    local moveTo = cc.MoveTo:create(0.5, cc.p(imageArr[i]:getPositionX(), numArr[i] * numHeight + numHeight));
                    imageArr[i]:runAction(moveTo);

                    imageArr[i].lastNum = numArr[i];
                end
            end
        end

        if coolTime > 0 then
            performWithDelay(timePanel, function ()
                refreshCoolTime(false);
            end, 1);
        end
    end
    refreshCoolTime(true);
end

-- 绘制能量按钮、进度条
function UISkyCity:redrawEnergy()
    -- 能量相关
    local maxEnergy = FormulaM.invoke("CALC_MAX_SHIP_ENERGY", ME.user);
    local curEnergy = ME.user.dbase:query("ship_energy", 0);

    -- "补充能量"按钮
    local kerning = -2;
    if not isChineseLang() then
        kerning = 0;
    end
    local CT = findChildByName(self.node, "CT");
    local screenNode = findChildByName(self.node, "Screen");
    local btnAddEnergy = findChildByName(screenNode, "energy_node/btn_add_energy");
    btnAddEnergy:setVisible(true);
    TextStyleM.setTextStyle(btnAddEnergy, TextStyleM.TEXT_SIZE_SMALL2, TextStyleM.TEXT_COLOR_BEIGE, true, kerning);
    btnAddEnergy:setTitleText(getLocStr("btn_text_add_energy"));
    local energyText = findChildByName(screenNode, "energy_node/energy_text");
    energyText:setVisible(true);
    TextStyleM.setTextStyle(energyText, TextStyleM.TEXT_SIZE_SMALL2, TextStyleM.TEXT_COLOR_BRIGHT_GREEN, true, -2);
    energyText:setString(string.format("%d/%d", curEnergy, maxEnergy));

    -- 能量条
    local curEnergyBar = findChildByName(screenNode, "energy_node/cur_energy");
    local percent = curEnergy * 100 / maxEnergy;

    if percent > 100 then
        percent = 100;
    elseif percent < 0 then
        percent = 0;
    end

    curEnergyBar:setPercent(percent);
    curEnergyBar:setVisible(true);

    -- 检测是否要播放探索按钮提示光效
    self:checkSelectBtnTipEffect();
end

-- 创建浮岛
function UISkyCity:createEventNode(pos)
    local eventInfo = self.eventsList[pos];

    -- 判断下是否有浮岛信息
    if not eventInfo then
        return;
    end

    local eventId = eventInfo.id;
    local frameIconName = string.format(FRAME_NAME, pos);
    local eventIconName = string.format(EVENT_NAME, pos);

    local showPos = SkyExploreM.parsePos(pos);
    local originX = self.radius * iconWidth;
    local originY = self.radius * iconHeight;
    local px = originX + (showPos.x / math.abs(showPos.x)) * (math.abs(showPos.x) - 0.5) * (iconWidth - 1);
    local py = originY + (showPos.y / math.abs(showPos.y)) * (math.abs(showPos.y) - 0.5) * (iconHeight - 1);

    -- 创建一个图片节点并且初始化
    local frameIcon = SkyExploreM.query(eventId, "frame_icon");
    local screenIcon = SkyExploreM.query(eventId, "screen_icon");

    local triggerId = eventInfo["trigger_id"];
    if type(triggerId) == "number" then
        -- 如果是触发事件，那么读取触发事件配置表中的边框和图标配置
        frameIcon = SkyExploreTriggerM.query(triggerId, "frame_icon");
        screenIcon = SkyExploreTriggerM.query(triggerId, "screen_icon");
    end

    local frameImg = ccui.ImageView:create();
    frameImg:setName("screen_node_img");
    frameImg:loadTexture(getSkyCityIconPath(frameIcon), 1);
    frameImg:setPosition(cc.p(px, py));
    frameImg.x = showPos.x;
    frameImg.y = showPos.y;
    frameImg.pos = pos;
    frameImg.icon = frameIcon;
    frameImg:setName(frameIconName);

    local iconImg = ccui.ImageView:create();

    if type(eventInfo["treasure_id"]) == "number" then
        screenIcon = "treasure" .. eventInfo["treasure_id"];
    end
    iconImg:loadTexture(getSkyCityIconPath(screenIcon), 1);
    iconImg:setPosition(cc.p(px, py));
    iconImg.x = showPos.x;
    iconImg.y = showPos.y;
    iconImg.pos = pos;
    iconImg.icon = screenIcon;
    iconImg:setName(eventIconName);

    -- 播放图标和外框的特效
    self:iconAnimation(screenIcon, iconImg, 1);
    self:frameAnimation(frameIcon, frameImg, 1);

    -- 处于冷却状态的话，显示为半透
    self:changeIconState(iconImg, frameImg, self:isPosInCoolTime(eventInfo));

    -- 注册点击回调
    frameImg:setTouchEnabled(true);
    self:registerScreenItemTouchEvent(frameImg);

    self.screen:addChild(frameImg);
    self.screen:addChild(iconImg);

    table.insert(self.eventIconList, frameIconName);
    table.insert(self.eventIconList, eventIconName);

    return frameImg, iconImg;
end

-- 更新浮岛节点
function UISkyCity:updateEvents(pos)
    local frameIconName = string.format(FRAME_NAME, pos);
    local eventIconName = string.format(EVENT_NAME, pos);

    if not self.eventsList[pos] then
        -- 从界面保存的列表中删除
        table.removeItem(self.eventIconList, frameIconName);
        table.removeItem(self.eventIconList, eventIconName);

        -- 从父节点删除
        self.screen:removeChildByName(frameIconName);
        self.screen:removeChildByName(eventIconName);

    else
        -- 如果数据还存在，那么更新下icon的图标
        local iconImg = self.screen:getChildByName(eventIconName);
        local frameImg = self.screen:getChildByName(frameIconName);

        local eventInfo = self.eventsList[pos];
        local eventId = eventInfo.id;
        local screenIcon = SkyExploreM.query(eventId, "screen_icon");
        local frameIcon = SkyExploreM.query(eventId, "frame_icon");

        local triggerId = eventInfo["trigger_id"];
        if type(triggerId) == "number" then
            -- 如果是触发事件，那么读取触发事件配置表中的边框和图标配置
            frameIcon = SkyExploreTriggerM.query(triggerId, "frame_icon");
            screenIcon = SkyExploreTriggerM.query(triggerId, "screen_icon");
        elseif type(eventInfo["treasure_id"]) == "number" then
            -- 如果是宝藏，特殊处理下icon
            screenIcon = "treasure" .. eventInfo["treasure_id"];
        end

        -- 重现加载图片资源
        iconImg:loadTexture(getSkyCityIconPath(screenIcon), 1);
        frameImg:loadTexture(getSkyCityIconPath(frameIcon), 1);

        -- 播放图标和外框的特效
        self:iconAnimation(screenIcon, iconImg, 1);
        self:frameAnimation(frameIcon, frameImg, 1);

        -- 处于冷却状态的话，显示为半透
        self:changeIconState(iconImg, frameImg, self:isPosInCoolTime(eventInfo));
    end
end

-- 绘制所有浮岛
function UISkyCity:redrawEvents()
    local allPosList = table.keys(self.eventsList);

    -- 先清空原先的浮岛图片
    for _, name in ipairs(self.eventIconList) do
        local child = self.screen:getChildByName(name);

        if child then
            self.screen:removeChild(child);
            child = nil;
        end
    end

    self.eventIconList = {};

    -- 创建新的浮岛图片
    for _, pos in ipairs(allPosList) do
        self:createEventNode(pos);
    end

    -- 绘制当前雷达点信息
    self:redrawRadarInfo();

    -- 重新绘制下焦点
    self:redrawFocus();
end

-- 调整所有浮岛的坐标
function UISkyCity:adjustEvents()
    -- 原点坐标
    local originX = self.radius * iconWidth;
    local originY = self.radius * iconHeight;

    for _, name in ipairs(self.eventIconList) do
        local child = self.screen:getChildByName(name);

        if child then
            local x = child.x;
            local y = child.y;
            local px = originX + (x / math.abs(x)) * (math.abs(x) - 0.5) * (iconWidth - 1);
            local py = originY + (y / math.abs(y)) * (math.abs(y) - 0.5) * (iconHeight - 1);

            child:setPosition(cc.p(px, py));
        end
    end
end

-- 绘制目标飞艇/浮岛图标或者光效
function UISkyCity:redrawShipOrIslandIcon(eventInfo)
    local airShipPanel = findChildByName(self.node, "CT/airship_panel");
    local airShip = findChildByName(self.node, "CT/air_ship");

    airShip:removeAllChildren();

    if not airShip.initPosY then
        airShip.initPosY = airShip:getPositionY();
    end

    -- 如果当前有在浮动，那么先干掉浮动
    if airShip.fluctuateAction then
        airShip:stopAction(airShip.fluctuateAction);
        airShip.fluctuateAction = nil;
        airShip:setPositionY(airShip.initPosY);
    end

    if eventInfo then
        local eventId = eventInfo.id;
        local eventConfig = SkyExploreM.query(eventId);

        -- 显示浮岛图片
        if type(eventInfo["trigger_id"]) == "number" then
            -- 触发事件
            local island = self:initTriggerIsland(eventInfo);
            island:setName("island");
            airShip:addChild(island);
        elseif eventConfig.rule == "player" then
            local shipNode = createAirShip(airShip, eventInfo["enemy"]["ship_id"]);
            -- 飞艇悬浮
            playFluctuateAnima(airShip, 0.1, 1);

            shipNode:setScale(0.8);
        elseif eventConfig.rule == "whirlpool" then
            local whirlpoolIsland = self:createWhirlpoolIsland(eventInfo.camp);
            whirlpoolIsland:setName("island");
            airShip:addChild(whirlpoolIsland);
        elseif eventConfig.rule == "boss" then
            local monsterDisplay = createSkyBossMonster(eventInfo["army"][1], true);
            monsterDisplay:setName("island");
            playFluctuateAnima(airShip, 0.1, 1);

            monsterDisplay:setScale(0.5);

            airShip:addChild(monsterDisplay);
        else
            local image = ccui.ImageView:create();
            image:setName("island");
            local island;

            -- 浮岛图片
            local islandType = eventInfo["island_type"];
            if type(eventInfo["treasure_id"]) == "number" then
                island, islandType = self:initIsland("temple" .. eventInfo["treasure_id"], islandType);
                self:setInitIslandType(eventInfo, islandType);

                if not island then
                    playFluctuateAnima(airShip, 1.5, 8);
                    image:loadTexture(getSkyCityIslandIconPath("temple" .. eventInfo["treasure_id"]));
                    airShip:addChild(image);
                end
            else
                island, islandType = self:initIsland(eventConfig["icon"], islandType);
                self:setInitIslandType(eventInfo, islandType);

                if not island then
                    playFluctuateAnima(airShip, 1.5, 8);
                    image:loadTexture(getSkyCityIslandIconPath(eventConfig["icon"]));
                    airShip:addChild(image);
                end

                -- 副本播放传送门光效
                if eventConfig["icon"] == "instance" then
                    local middle = findChildByName(island, "middle_building");
                    local _, effect = playEffect(middle, 1102, 130, -130);
                    effect:setScale(0.7);
                end
            end

            if island then airShip:addChild(island); end
        end

        airShip:setVisible(true);
        airShipPanel:setVisible(true);

        -- 如果是试练塔，那么不需要显示浮岛或者飞艇
        if eventId == SKY_TOWER then
            airShip:setVisible(false);
        end
    else
        airShip:setVisible(false);
        airShipPanel:setVisible(false);
    end
end

-- 绘制飞艇信息
function UISkyCity:redrawAirShipInfo(eventInfo)
    local eventId = eventInfo.id;
    local eventConfig = SkyExploreM.query(eventId);
    local combat_dbase = eventConfig["combat_dbase"] or {};
    local airShipInfoNode = findChildByName(self.node, "CT/airship_info");

    -- 绘制信息
    local nameLabel = airShipInfoNode:getChildByName("name");
    local typeLabel = airShipInfoNode:getChildByName("type");
    local scoreLabel = airShipInfoNode:getChildByName("score");

    nameLabel:setFontSize(TextStyleM.TEXT_SIZE_MINI);
    nameLabel:setTextColor(TextStyleM.TEXT_COLOR_BRIGHT_BLUE);
    TextStyleM.setOutlineStyle(nameLabel, 2, TextStyleM.TEXT_COLOR_DARK_BLUE2, true);

    typeLabel:setFontSize(TextStyleM.TEXT_SIZE_MINI);
    typeLabel:setTextColor(TextStyleM.TEXT_COLOR_BRIGHT_BLUE);
    TextStyleM.setOutlineStyle(typeLabel, 2, TextStyleM.TEXT_COLOR_DARK_BLUE2, true);

    scoreLabel:setFontSize(TextStyleM.TEXT_SIZE_MINI);
    scoreLabel:setTextColor(TextStyleM.TEXT_COLOR_BRIGHT_BLUE);
    TextStyleM.setOutlineStyle(scoreLabel, 2, TextStyleM.TEXT_COLOR_DARK_BLUE2, true);

    local line5 = airShipInfoNode:getChildByName("line5");
    local line6 = airShipInfoNode:getChildByName("line6");
    if type(eventInfo["trigger_id"]) == "number" then
        -- 特殊事件
        nameLabel:setVisible(false);
        line5:setVisible(false);
        line6:setVisible(false);
        local name = SkyExploreTriggerM.query(eventInfo.trigger_id, "name");

        -- 飞艇残骸的话要加上飞艇的名称
        if SkyExploreTriggerM.query(eventInfo.trigger_id, "rule") == "ship_remains" then
            name = spliceLocStr(NewAirShipM.query(eventInfo.ship_id, "name"), name);
        end

        typeLabel:setString(name);
        scoreLabel:setVisible(true);
        scoreLabel:setString(string.format(getLocStr("talent_level_format"), eventInfo.explore_level));
    elseif eventConfig["rule"] == "resource" or eventConfig["rule"] == "instance" then
        -- 资源浮岛
        nameLabel:setVisible(false);
        line5:setVisible(false);
        line6:setVisible(false);
        local name = eventConfig["name"];
        if eventInfo.treasure_id then
            -- 探索到神殿
            name = SkyResourceM.querySkyTreasure(eventInfo.treasure_id, "name");
        end
        typeLabel:setString(name);
        scoreLabel:setVisible(true);
        scoreLabel:setString(string.format(getLocStr("talent_level_format"), eventInfo.explore_level));
    elseif eventConfig["rule"] == "usage" then
        nameLabel:setVisible(false);
        line5:setVisible(false);
        line6:setVisible(false);

        name = eventConfig["name"];

        if eventId == SKY_TOWER then
            local camp = ME.user.dbase:query("activedSkyGroup");
            name = CampM.query(camp, "sky_tower_name");

            scoreLabel:setVisible(true);
            scoreLabel:setString(string.format(getLocStr("sky_tower_floor"), SkyTowerM.getMaxFloor(camp)));
        else
            scoreLabel:setVisible(false);
        end

        typeLabel:setString(name);

    elseif eventConfig["rule"] == "whirlpool" then
        nameLabel:setVisible(false);
        line5:setVisible(false);
        line6:setVisible(false);

        name = eventConfig["name"];

        --获得当前的阵营
        local needCamp = eventInfo["camp"];
        name = string.format(getLocStr("whirlpool_title"), CampM.query(needCamp, "whirlpool_color"));

        typeLabel:setString(name);

        scoreLabel:setString(string.format(getLocStr("talent_level_format"), eventInfo.explore_level));
        scoreLabel:setVisible(true);
    elseif eventConfig["rule"] == "sky_activity" then
        -- 天空活动
        nameLabel:setVisible(false);
        typeLabel:setVisible(true);
        scoreLabel:setVisible(true);
        line5:setVisible(false);
        line6:setVisible(false);
        local activityId = eventInfo.activity_id;
        typeLabel:setString(SkyActivityM.query(activityId, "name"));
        scoreLabel:setString(getLocStr("sky_activity_suffix"));
    else
        nameLabel:setVisible(true);
        line5:setVisible(true);
        line6:setVisible(true);
        local abilityNum = FormulaM.invoke("CALC_SKY_MONSTER_FIGHT_ABILITY",
            eventId, eventInfo["explore_level"], combat_dbase["ability_fix"]);
        if eventConfig["rule"] == "player" then
            -- 玩家姓名
            nameLabel:setString(getNickName(eventInfo["enemy"].name));
            abilityNum =eventInfo["enemy"].fight_ability;
        else
            nameLabel:setString(getNickName(eventConfig["name"]));
        end
        typeLabel:setString(string.format(getLocStr("talent_level_format"), eventInfo.explore_level));
        scoreLabel:setVisible(true);
        scoreLabel:setString(string.format(getLocStr("ap_desc"), abilityNum));
    end
end

-- 绘制飞船的按钮
function UISkyCity:redrawAirShipBtn(eventInfo)
    local eventId = eventInfo.id;
    local eventConfig = SkyExploreM.query(eventId);
    local combat_dbase = eventConfig["combat_dbase"] or {};
    local airShipBtn = findChildByName(self.node, "CT/airship_btn");

    -- 绘制按钮
    local btnDoEvent = findChildByName(airShipBtn, "do_event/btn_do_event");
    local btnInfo = findChildByName(airShipBtn, "info/btn_info");
    local infoNode = findChildByName(airShipBtn, "info");

    -- 只有怪物/BOSS/玩家需要显示
    if table.indexOf({"player", "boss", "monster", "whirlpool"}, eventConfig["rule"]) ~= -1 then
        infoNode:setVisible(true);
    else
        infoNode:setVisible(false);
    end

    btnDoEvent:setTitleFontSize(TextStyleM.TEXT_SIZE_MINI);
    btnDoEvent:setTitleColor(TextStyleM.TEXT_COLOR_BRIGHT_BLUE);
    TextStyleM.setOutlineStyle(btnDoEvent, 2, TextStyleM.TEXT_COLOR_DARK_BLUE2, true);

    btnInfo:setTitleFontSize(TextStyleM.TEXT_SIZE_MINI);
    btnInfo:setTitleColor(TextStyleM.TEXT_COLOR_BRIGHT_BLUE);
    TextStyleM.setOutlineStyle(btnInfo, 2, TextStyleM.TEXT_COLOR_DARK_BLUE2, true);

    -- 试炼塔显示"信息"
    if SkyExploreM.query(eventId, "screen_icon") == "tower" then
        btnInfo:setTitleText(getLocStr("btn_text_info2"));
    else
        btnInfo:setTitleText(getLocStr("btn_text_info"));
    end

    if eventConfig["rule"] == "resource" then
        if type(eventInfo.treasure_id) == "number" then
            btnDoEvent:setTitleText(getLocStr("btn_text_open_treasure"));
        else
            btnDoEvent:setTitleText(getLocStr("btn_text_search"));
        end
    elseif eventConfig["rule"] == "usage" then
        self:redrawUsageIslandBtn(eventInfo["id"]);
    elseif eventConfig["rule"] == "instance" then
        btnDoEvent:setTitleText(getLocStr("btn_text_enter"));
    elseif eventConfig["rule"] == "sky_activity" then
        btnDoEvent:setTitleText(getLocStr("btn_text_close_to"));
    else
        btnDoEvent:setTitleText(getLocStr("btn_text_attack"));
    end

    if type(eventInfo["trigger_id"]) == "number" then
        infoNode:setVisible(false);

        if SkyExploreTriggerM.query(eventInfo["trigger_id"], "rule") == "question" then
            btnDoEvent:setTitleText(getLocStr("btn_text_trigger_question"));
        else
            btnDoEvent:setTitleText(getLocStr("btn_text_trigger_event"));
        end
    end

    -- 播放下目标浮岛/飞艇出现的特效
    self:playAirshipBtnEffect(btnDoEvent);
    self:playAirshipBtnEffect(btnInfo);

    -- 播放准心的特效
    if self.talkTemplate == nil then
        self:playReticleEffect();
    else
        local reticleNode = findChildByName(self.node, "CT/reticle_node");
        reticleNode:setVisible(false);
        local btns = findChildByName(self.node, "CT/airship_btn");
        btns:setVisible(false);

        -- 飞船信息节点
        local airShipInfoNode = findChildByName(self.node, "CT/airship_info");
        airShipInfoNode:setVisible(false);
    end
end

-- 绘制飞船及其信息
function UISkyCity:redrawAirShip(noCloud, isOld, noDrawShip)
    local node = self.node;
    local CT = findChildByName(node, "CT");
    local eventInfo = nil;

    if isOld == true then
        eventInfo = self.exploreRecord;
    else
        if self.selectPos then
            eventInfo = self.eventsList[self.selectPos];
        end
    end

    -- 背景
    local skyBg = findChildByName(self.node, "CT/sky_bg");
    skyBg:setVisible(false);

    -- 使用岛屿与云彩
    local background = findChildByName(CT, "background");
    background:setVisible(true);

    -- 先将background恢复到初始坐标
    if not background.initPosY then
        background.initPosY = background:getPositionY();
    end
    background:setPositionY(background.initPosY);

    -- 背景上下晃动
    background:stopAction(background.fluctuateAction);
    playFluctuateAnima(background, 2, 5);

    if not self.initBackGround then
        self.initBackGround = true;

        -- 加载天空和海面
        local oceanNode = findChildByName(background, "ocean_node");
        local skyNode = findChildByName(background, "sky_node");

        local skyId = math.random(SKY_TEMPLATE_NUM);
        local node = cc.CSLoader:createNode(string.format("layout/sky_city/background/background_sky_%d.csb", skyId));
        node.id = skyId;
        node:setName("sky1");
        skyNode:addChild(node);

        local id = math.random(OCEAN_TEMPLATE_NUM);
        if eventInfo then
            id = eventInfo["ocean_id"] or 1;
        end
        local node = cc.CSLoader:createNode(string.format("layout/sky_city/background/background_ocean_%d.csb", id));
        node.id = id;
        node:setName("ocean1");
        oceanNode:addChild(node);
    end

    -- 播放热气球升空效果
    self:playFireBalloonEffect();

    -- 播放漂浮的云层效果
    self:playFloatingCloudEffect(not noCloud);

    -- 绘制目标飞艇/浮岛
    if not noDrawShip then
        self:redrawShipOrIslandIcon(eventInfo);
    end

    -- 准心节点
    local reticleNode = findChildByName(CT, "reticle_node");
    -- 飞船信息节点
    local airShipInfoNode = findChildByName(CT, "airship_info");
    -- 飞船按钮节点
    local airShipBtn = findChildByName(CT, "airship_btn");

    if eventInfo then
        --选择了目标，显示目标的信息
        airShipInfoNode:setVisible(true);
        airShipBtn:setVisible(true);

        -- 绘制飞艇信息
        self:redrawAirShipInfo(eventInfo);
        -- 绘制飞船的按钮
        self:redrawAirShipBtn(eventInfo);
    else
        -- 没有选中目标，或者失去了目标

        -- 隐藏准心、飞船信息、飞船按钮
        reticleNode:setVisible(false);
        airShipInfoNode:setVisible(false);
        airShipBtn:setVisible(false);
    end

    if self.talkTemplate ~= nil and self.flag == false then
        self.flag = true;
        local function callback()
            self.talkTemplate = nil;
            self:playReticleEffect();
            airShipInfoNode:setVisible(true);
            airShipBtn:setVisible(true);
            self.flag = false;

            -- 如果有奖励，则播放飞入仓库的动画
            if self.guideBonus then
                self:playSkyCityBonusEffect(self.guideBonus);
                self.guideBonus = nil;
            end
        end
        local function startGuide()
            ME.user.dbase:deleteTemp("explore_need_guide");

            startDialogueTemplate(self.talkTemplate, callback);
            UIMgr.getCurrentScene():removeFormByName("UIInvisibleForm");

        end

        performWithDelay(self, startGuide, 1.3);
    end
end

-- 屏幕格子节点载入图片
function UISkyCity:gridLoadTexture(grid)
    local x = grid.x;
    local y = grid.y;
    local lastImage = grid.image;

    if x < -1 - self.radius or x > 1 + self.radius or y < -1 - self.radius or y > 1 + self.radius then
        -- 外围直接使用纯色迷雾图片
        grid.image = "mask2";
    elseif x == -1 - self.radius and y <= self.radius and y >= -1 * self.radius then
        -- 左侧使用渐变迷雾图片，左深色右浅色
        grid.image = "mask1";
        grid:setRotation(90);
    elseif x == 1 + self.radius and y <= self.radius and y >= -1 * self.radius then
        -- 右侧使用渐变迷雾图片，右深色左浅色
        grid.image = "mask1";
        grid:setRotation(-90);
    elseif y == -1 - self.radius and x <= self.radius and x >= -1 * self.radius then
        -- 下侧使用渐变迷雾图片，下深色上浅色
        grid.image = "mask1";
    elseif y == 1 + self.radius and x <= self.radius and x >= -1 * self.radius then
        -- 上侧使用渐变迷雾图片，上深色下浅色
        grid.image = "mask1";
        grid:setRotation(180);
    elseif x == -1 - self.radius and y == 1 + self.radius then
        -- 左上角
        grid.image = "mask3";
        grid:setRotation(180);
    elseif x == -1 - self.radius and y == -1 - self.radius then
        -- 左下角
        grid.image = "mask3";
        grid:setRotation(90);
    elseif x == 1 + self.radius and y == 1 + self.radius then
        -- 右上角
        grid.image = "mask3";
        grid:setRotation(-90);
    elseif x == 1 + self.radius and y == -1 - self.radius then
        -- 右下角
        grid.image = "mask3";
    else
        grid.image = "grid";
        grid:setRotation(0);
    end

    grid:loadTexture(getSkyCityPath(grid.image), 1);
end

-- 初始化浮岛数据
function UISkyCity:initData()
    self.eventsList = {};

    local radius = self.radius or 0;
    local data = SkyExploreM.getExploreEvents() or {};
    for index, info in pairs(data) do
        local temp = SkyExploreM.calcEventPos(index);
        temp = table.add(temp, info);
        temp["index"] = index;

        self.eventsList[temp.pos] = temp;

        -- 重置下生成浮岛的类型
        if temp and self.eventsList[temp.pos]["island_type"] then
            self.eventsList[temp.pos]["island_type"] = nil;
        end

        radius = math.max(radius, math.abs(temp.x) + 1, math.abs(temp.y) + 1);
    end

    self.radius = math.max(radius, MIN_RADIUS);

    -- 添加上当前固定浮岛信息
    local skyExploreData = SkyExploreM.getForeverIslandData();

    if #skyExploreData > 0 then
        for k, v in pairs(skyExploreData) do
            local temp1 = SkyExploreM.parsePos(v.pos);
            temp1 = table.add(temp1, v);
            temp1["index"] = k;

            self.eventsList[temp1.pos] = temp1;

            -- 重置下生成浮岛的类型
            if temp1 and self.eventsList[temp1.pos]["island_type"] then
                self.eventsList[temp1.pos]["island_type"] = nil;
            end
        end
    end
end

-- 初始化浮岛列表
function UISkyCity:initGrid()
    -- 总个数
    local radius = self.radius + EXTRA_NUM;
    local rowNum = radius * 2;
    local totalCount = math.pow(rowNum, 2);

    -- 计算单元格分布情况
    local gridList = sortGridsEx(totalCount, rowNum, rowNum);

    -- 先计算总高度
    local totalHeight = rowNum * iconHeight;
    local totalWidth  = rowNum * iconWidth;

    -- 设置滚动区域大小
    local scrollNode = self.screen;
    scrollNode:setInnerContainerSize(cc.size(totalWidth - 2 * EXTRA_NUM * iconWidth, totalHeight - 2 * EXTRA_NUM * iconHeight));

    local widget = ccui.ImageView:create();

    -- 先算出原点的x,y
    local originX = self.radius * iconWidth;
    local originY = self.radius * iconHeight;
    for x = -1 * radius, radius do
        for y = -1 * radius, radius do
            repeat
                if x == 0 or y == 0 then
                    break;
                end

                local name = string.format(ITEM_NAME, x, y);
                -- 计算单元格坐标

                -- 这个坐标将是单元格的描点所在
                -- 这里是个COCOS合图的坑，会偏个一像素，所以只能在代码里面主动-1了，TNND
                local px = originX + (x / math.abs(x)) * (math.abs(x) - 0.5) * (iconWidth - 1);
                local py = originY + (y / math.abs(y)) * (math.abs(y) - 0.5) * (iconHeight - 1);

                local grid = findChildByName(scrollNode, name);
                if grid then
                    -- 调整下坐标
                    grid:setPositionX(px);
                    grid:setPositionY(py);
                    self:gridLoadTexture(grid);
                    break;
                end

                local child = widget:clone();
                child:setName(name);
                child.pos   = SkyExploreM.calcPosByXY(x, y);
                child.x     = x;
                child.y     = y;
                self:gridLoadTexture(child);

                if math.abs(x) == 1 and math.abs(y) == 1 then
                    child:setVisible(false);
                end

                -- 设置坐标
                child:setPositionX(px);
                child:setPositionY(py);

                scrollNode:addChild(child);
            until true;
        end
    end

    -- 创建一个中心点方框
    local icon = findChildByName(self.screen, "center_point");
    if not icon then
        local icon = ccui.ImageView:create();
        icon:setName("center_point");
        icon:loadTexture(getSkyCityPath("center"), 1);
        icon:setAnchorPoint(0.5, 0.5);
        icon:setPosition(cc.p(originX, originY));
        self.screen:addChild(icon);
    else
        icon:setPosition(cc.p(originX, originY));
    end

    -- 播放屏幕中心方框特效
    local squareImg = findChildByName(self.screen, "center_square");
    if not squareImg then
        local squareImg = ccui.ImageView:create();
        squareImg:setName("center_square");
        squareImg:loadTexture(getSkyCityPath("center_square"), 1);
        squareImg:setAnchorPoint(0.5, 0.5);
        squareImg:setPosition(cc.p(originX, originY));
        squareImg:setOpacity(150);
        self.screen:addChild(squareImg);

        local scaleTo = cc.ScaleTo:create(1, 2);
        local fadeOut = cc.FadeOut:create(1);
        local delay = cc.DelayTime:create(0.5);
        local callFunc = cc.CallFunc:create(function ()
            squareImg:setScale(1);
            squareImg:setOpacity(150);
        end);

        squareImg:runAction(cc.RepeatForever:create(cc.Sequence:create(cc.Spawn:create(scaleTo, fadeOut), delay, callFunc)));
    else
        squareImg:setPosition(cc.p(originX, originY));
    end
end

-- 点击浮岛的回调
function UISkyCity:onScreenItemClick(pos)
    local lastSelected = self.selectPos;

    self.selectPos = pos;

    self:screenSelected(lastSelected, self.selectPos);

    -- 移动屏幕
    local delay = self:scrollScreenToPos(lastSelected, self.selectPos);
    return delay;
end

-- 绘制选中框
function UISkyCity:redrawFocus()
    local focusImg = findChildByName(self.screen, "focus");
    local pos = self.selectPos;
    local showPos = SkyExploreM.parsePos(pos);

    if not focusImg then
        local icon = ccui.ImageView:create();
        icon:setName("focus");
        icon:loadTexture(getSkyCityPath("grid_effect"), 1);
        icon:setAnchorPoint(0.5, 0.5);
        self.screen:addChild(icon);

        focusImg = findChildByName(self.screen, "focus");
    end

    if pos > 0 and self.eventsList[pos] then
        focusImg:setVisible(true);
        local originX = self.radius * iconWidth;
        local originY = self.radius * iconHeight;
        local px = originX + (showPos.x / math.abs(showPos.x)) * (math.abs(showPos.x) - 0.5) * (iconWidth - 1);
        local py = originY + (showPos.y / math.abs(showPos.y)) * (math.abs(showPos.y) - 0.5) * (iconHeight - 1);

        focusImg:setPosition(cc.p(px, py));
    else
        focusImg:setVisible(false);
    end
end

-- 注册浮岛点击回调
function UISkyCity:registerScreenItemTouchEvent(node)
    local function onClick(sender, eventType)
        -- 点击时的回调
        if eventType == ccui.TouchEventType.ended then

            local pos = node.pos;
            -- 打印下pos
            print("选择的目标pos = " .. pos .. ", x = " .. node.x .. ", y = " .. node.y);

            if self.eventsList[pos] == nil then
                return;
            end

            AudioM.playFx("select_radar");

            self:onScreenItemClick(pos);
        end
    end

    node:addTouchEventListener(onClick);
end

-- 浮岛选中效果
function UISkyCity:screenSelected(lastSelected, nowSelected)
    self:redrawFocus();
    local focusImg = findChildByName(self.screen, "focus");
    focusImg:stopAllActions();

    -- 新选中的效果
    -- 放大、缩小、震动一下然后开始呼吸
    focusImg:setScale(1.5);
    local scaleDown1 = cc.ScaleTo:create(0.2, 0.95);
    local scaleUp1 = cc.ScaleTo:create(0.2, 1.05);
    local scaleDown2 = cc.ScaleTo:create(0.2, 0.95);

    focusImg:runAction(cc.Sequence:create(scaleDown1, scaleUp1, scaleDown2));

    performWithDelay(focusImg, function ( ... )
        local scaleUp = cc.ScaleTo:create(0.7, 1.05);
        local scaleDown = cc.ScaleTo:create(0.7, 0.95);
        local delay = cc.DelayTime:create(0.2);
        local action = cc.Sequence:create(scaleUp, scaleDown, delay);

        focusImg:runAction(cc.RepeatForever:create(action));
    end, 0.6);
end

resize = function(self)
    local node = self.node;
    local w = AlignM.frameSize.width;
    local h = AlignM.frameSize.height;

    -- bg居中显示并充满整个屏幕
    AlignM.fitToScreen(node, "bg_mask");

    -- CT居中并缩放至合适大小
    AlignM.alignToCenter(node, "CT");
    AlignM.alignToCenter(node, "form_mask");
    AlignM.alignToCenter(node, "effect");
    AlignM.alignToCenter(node, "black_mask");

    -- BT置于底部并缩放至合适大小
    AlignM.alignToBottomCenter(node, "BT");

    -- 屏幕区域置于底部并缩放至合适大小
    AlignM.alignToBottomCenter(node, "Screen");

    -- 顶部区域至于顶部并缩放至合适大小
    AlignM.alignToTopCenter(node, "Top");

    -- 天空需要处理一下
    local CT = findChildByName(node, "CT");
    local CTScale = CT:getScale();

    local desertImg = findChildByName(node, "CT/sky_bg");
    local skyNode = findChildByName(node, "CT/background");
    local skyBgImg = findChildByName(node, "CT/background/sky_bg");
    local shipFrameNode = findChildByName(node, "Screen/ship_frame");
    local maskLeft = findChildByName(node, "CT/sky_mask_left");
    local maskRight = findChildByName(node, "CT/sky_mask_right");

    local worldPos = shipFrameNode:convertToWorldSpaceAR(cc.p(0, 0));
    local lastHeight = DESIGN_HEIGHT / 2;
    local curHeight = h / 2 + (h / 2 - worldPos.y) / CTScale;

    local scale = math.max(w / DESIGN_WIDTH, curHeight / lastHeight);

    -- x,y要分别放大的节点
    desertImg:setScaleX(w / DESIGN_WIDTH / CTScale);
    desertImg:setScaleY(curHeight / lastHeight / CTScale);
    maskLeft:setScaleX(w / DESIGN_WIDTH / CTScale);
    maskRight:setScaleX(w / DESIGN_WIDTH / CTScale);
    maskLeft:setScaleY(curHeight / lastHeight / CTScale);
    maskRight:setScaleY(curHeight / lastHeight / CTScale);

    -- 需要调整下坐标
    desertImg:setPositionY(h / 2 - curHeight);
    skyNode:setPositionY(h / 2 - curHeight);
    maskLeft:setPosition(w / -2 / CTScale, h / 2 - curHeight);
    maskRight:setPosition(w / 2 / CTScale, h / 2 - curHeight);

    -- x,y等比放大的节点
    skyNode:setScale(scale / CTScale);
end

-- 绘制雷达点信息
function UISkyCity:redrawRadarInfo()
    -- 当前雷达点信息
    local radarInfo = findChildByName(self.node, "Screen/radar_info");
    local iconImg = findChildByName(self.node, "Screen/radar_info/icon");
    local radarLabel = findChildByName(self.node, "Screen/radar_info/text");
    local allPosList = table.keys(self.eventsList);

    -- 指引未完成的话，则隐藏雷达信息点
    if not NewAirShipM.isFinishGuide() then
        radarInfo:setVisible(false);
        return;
    end

    -- 设置雷达信息点可见
    radarInfo:setVisible(true);

    radarLabel:setFontSize(TextStyleM.TEXT_SIZE_TINY);
    radarLabel:setTextColor(TextStyleM.TEXT_COLOR_BRIGHT_GREEN);
    TextStyleM.setOutlineStyle(radarLabel);

    -- 当前雷达次数
    local times = SkyExploreM.calcExploreTimes();
    radarLabel:setString(string.format("%02d/%02d", times, SkyExploreM.maxExploreTimes()));

    -- 如果当前有浮岛，那么跳动一下
    iconImg:stopAllActions();
    if #allPosList > 0 and not self.isStopAllEffects then
        local scaleUp = cc.ScaleTo:create(0.5, 1.1);
        local scaleDown = cc.ScaleTo:create(0.5, 1);
        local delay = cc.DelayTime:create(0.2);
        local action = cc.Sequence:create(scaleUp, scaleDown, delay);

        iconImg:runAction(cc.RepeatForever:create(action));
    end
end

-- 尝试触发指引
function UISkyCity:tryToGuide()
    local curProgress = NewAirShipM.getGuideProgress();
    if curProgress <= 1 then
        -- 对话结束回调
        local function callback()
            NewAirShipM.setGuideProgress(2);
        end

        -- 未获得舰长冈布奥，进行舰长冈布奥对白
        startDialogueTemplate("sky_city_talk1", callback);
        self.repairTip = true;
    elseif curProgress == 2 then
        NewAirShipM.setGuideProgress(3);

        self.repairTip = true;
    elseif curProgress == 3 then
        -- 收集奖励信息
        BonusM.clearBonusCollect();
        BonusM.startBonusCollect();

        NewAirShipM.setGuideProgress(4);

        BonusM.endBonusCollect();

        local bonusList = table.deepcopy(BonusM.getBonusCollect());
        BonusM.clearBonusCollect();

        -- 弹出奖励提示框
        showBonusNotifyForm(getLocStr("sky_city_guaide_bonus_title"), bonusList, nil, nil, nil, nil, nil, nil, {["mergePuzzle"] = true});

        self.repairTip = true;
    elseif curProgress == 5 then
        startDialogueTemplate("flyin_sky", function ()
            NewAirShipM.setGuideProgress(6);
        end);

        UIMgr:getCurrentScene().topMenu:setVisible(true);
    elseif not NewAirShipM.isFinishGuide() then
        if not self.repairTip then
            -- 未修理，提示玩家修理
            startDialogueTemplate("sky_city_talk2");
            self.repairTip = true;
        end
    end
end

-- 播放探索特效
function UISkyCity:playSelectEffect()
    local initX = -80;
    local endX = 80;
    local selectBar = findChildByName(self.node, "Screen/select_node/progress");
    local effectIcon = findChildByName(self.node, "Screen/select_node/effect");

    selectBar:setPercent(0);
    selectBar:stopAllActions();
    effectIcon:stopAllActions();
    effectIcon:setVisible(true);
    effectIcon:setPositionX(initX);

    local index = 0;
    local times = 20;
    local function barScroll(node)
        node:setPercent(index);

        local moveTo = cc.MoveTo:create(1 / times, cc.p(initX + (endX - initX) * index / 100, effectIcon:getPositionY()));
        effectIcon:runAction(moveTo);

        index = index + 100 / times;

        if index > 100 then
            index = 0;
            node:setPercent(0);
            effectIcon:setPositionX(initX);

            if self.exploreEnd then
                effectIcon:stopAllActions();
                effectIcon:setVisible(false);

                self:doExploreResult();
                self.exploreEnd = nil;
            else
                barScroll(node);
            end
        else
            performWithDelay(node, barScroll, 1 / times);
        end
    end

    -- 进度条变化
    barScroll(selectBar);
end

-- 补全未显示的目标
function UISkyCity:fillEvents(newPos)
    for _, pos in ipairs(table.keys(self.eventsList)) do
        local name = string.format(EVENT_NAME, pos);
        -- 不是新增的节点，并且未创建，那么补全
        if pos ~= newPos and not self.screen:getChildByName(name) then
            self:createEventNode(pos);
        end
    end
end

-- 探索结果特效
function UISkyCity:doExploreResult()
    self:redrawEnergy();

    -- 这里停止所有的行为是为了避免玩家连续点击探索按钮，导致连续绘制多个浮岛
    self:stopAllActions();

    self:setEventBtnDisable(false);

    -- 隐藏焦点图标
    local focusImg = findChildByName(self.screen, "focus");
    if focusImg then
        focusImg:setVisible(false);
    end

    local oldRadius = self.radius;
    self:initData();
    local newRadius = self.radius;

    if newRadius > oldRadius then
        -- 再初始化下格子
        self:initGrid();

        -- 调整所有浮岛的坐标
        self:adjustEvents();
    end

    -- 跳转到新发现的浮岛
    local data = self.newData.data;

    -- 补全未显示的浮岛
    self:fillEvents(data.pos);

    -- 将新的浮岛设置为选中的状态
    local lastSelected = self.selectPos;

    -- 移动屏幕
    local delay = self:scrollScreenToPos(lastSelected, data.pos, true);

    -- 播放浮岛出现特效
    local function playEventAppear()
        -- 创建浮岛节点
        local frameImg, iconImg = self:createEventNode(data.pos);

        -- 播放完屏幕移动的效果之后才去修改selectPos
        -- 因为这时候原浮岛及其按钮已经消失，玩家无法点击到袭击按钮
        -- 如果在移动前就修改了selectPos，那么有可能玩家在移动开始之前点击了袭击按钮
        -- 袭击成功将导致新出现浮岛的信息被干掉。这时候还在播放浮岛出现的特效，准备创建新浮岛时，将获取不到浮岛信息
        self.selectPos = data.pos;

        if frameImg and iconImg then
            frameImg:setScale(0.1);
            iconImg:setScale(0.1);

            local scaleUp1 = cc.EaseOut:create(cc.ScaleTo:create(0.5, 1), 2);
            local scaleUp2 = cc.EaseOut:create(cc.ScaleTo:create(0.5, 1), 2);
            frameImg:runAction(cc.Sequence:create(scaleUp1));
            iconImg:runAction(cc.Sequence:create(scaleUp2));

        performWithDelay(frameImg, function ()
                if action then
                    frameImg:runAction(cc.RepeatForever:create(action));
                end

                self:screenSelected(lastSelected, self.selectPos);
            end, 0.5);
        end

        -- 重绘下飞船的信息
        self:redrawAirShip();

        -- 绘制当前雷达点信息
        self:redrawRadarInfo();
    end

    -- 屏幕移动结束后浮岛出现
    performWithDelay(self, playEventAppear, delay);
end

-- 屏幕滚动到指定的坐标
function UISkyCity:scrollScreenToPos(lastSelected, curSelect, isNew)
    local curPos = SkyExploreM.parsePos(curSelect);
    local lastPos = SkyExploreM.parsePos(lastSelected);

    -- 先算出原点的坐标
    local originX = self.radius * iconWidth;
    local originY = self.radius * iconHeight;

    local viewWidth = self.screen:getContentSize().width;
    local viewHeight = self.screen:getContentSize().height;

    -- 算出目标格子在容器中的坐标
    local px = originX + (curPos.x / math.abs(curPos.x)) * (math.abs(curPos.x) - 0.5) * (iconWidth - 1);
    local py = originY + (curPos.y / math.abs(curPos.y)) * (math.abs(curPos.y) - 0.5) * (iconHeight - 1);
    -- 这样需要算出的是容器左下角相对于显示区域左下角的坐标偏移量
    local destinationX = viewWidth / 2 - px;
    local destinationY = viewHeight / 2 - py;

    -- 最多只能移动到边界
    local max = 0;
    local min = viewWidth - self.screen:getInnerContainerSize().width;
    if destinationX > max then
        destinationX = max;
    end
    if destinationX < min then
        destinationX = min;
    end

    -- 获取当前的x,y坐标
    local lastPx = self.screen:getInnerContainer():getPositionX();
    local lastPy = self.screen:getInnerContainer():getPositionY();
    -- 算出移动的直线距离
    local length = math.sqrt(math.pow(math.abs(lastPx - destinationX), 2) + math.pow(math.abs(lastPy - destinationY), 2));
    local delay = 1.5;

    -- 直线移动
    local moveTo = cc.MoveTo:create(delay, cc.p(destinationX, destinationY));

    self.screen:getInnerContainer():stopAllActions();
    self.screen:getInnerContainer():runAction(moveTo);

    -- 转方向盘
    self:turnSteeringWheel(curSelect, delay, isNew);

    -- 屏幕风景移动
    self:sceneryMove(1, curSelect, lastSelected, isNew);

    return delay;
end

-- 屏幕风景移动时，飞艇移动效果
function UISkyCity:playShipMoveEffect(curSelect, direction, delay)
    local airShip = findChildByName(self.node, "CT/air_ship");
    -- 目标移动出去并且变小随后从另一侧飞入
    if not airShip.initX then
        airShip.initX = airShip:getPositionX();
    end

    local function resetAirship()
        airShip:setPositionX(airShip.initX);
    end

    local moveBy = cc.EaseInOut:create(cc.MoveBy:create(delay / 3, cc.p(AlignM.frameSize.width * direction, 0)), 2.5);
    local scaleTo = cc.EaseInOut:create(cc.ScaleTo:create(delay / 3, 0.1), 2.5);
    local moveBy2 = cc.EaseInOut:create(cc.MoveBy:create(delay / 3 * 2, cc.p(960 * (MOVE_OCEAN_NUM - 1) * direction, 0)), 2.5);
    local flyInCallFunc = cc.CallFunc:create(function ()
        airShip:setPositionX(airShip.initX + direction * (MOVE_OCEAN_NUM - 1) * -960);

        -- 绘制下飞艇
        local eventInfo = self.eventsList[curSelect];
        self:redrawShipOrIslandIcon(eventInfo);
        setOpacity(airShip, 100);
    end);
    local resetCallFunc = cc.CallFunc:create(resetAirship);

    airShip:runAction(cc.Sequence:create(cc.Spawn:create(moveBy, scaleTo), flyInCallFunc, moveBy2, resetCallFunc));
end

-- 屏幕风景移动时，海面移动效果
function UISkyCity:playOceanMoveEffect(curSelect, direction, delay)
    -- 海面移动（较快）
    local background = findChildByName(self.node, "CT/background");
    local oceanNode = findChildByName(self.node, "CT/background/ocean_node");

    -- 创建多个海面
    for index = 1, MOVE_OCEAN_NUM do
        local node;

        if index == MOVE_OCEAN_NUM then
            node = cc.CSLoader:createNode(string.format("layout/sky_city/background/background_ocean_%d.csb", math.random(OCEAN_TEMPLATE_NUM)));
        else
            node = cc.CSLoader:createNode(string.format("layout/sky_city/background/background_ocean_transition_%d.csb", index));
        end
        node:setName("ocean" .. index + 1);
        node:setPositionX(-960 * index * direction);

        oceanNode:addChild(node);
    end

    local function resetOceanNode()
        local lastOcean = oceanNode:getChildByName("ocean" .. MOVE_OCEAN_NUM + 1);

        -- 把前面的ocean都干掉，只保留最后一个
        for index = 1, MOVE_OCEAN_NUM do
            oceanNode:removeChildByName("ocean" .. index);
        end

        -- 最后一个节点偷偷变成第一个节点，并且修改x轴坐标
        lastOcean:setName("ocean1");
        lastOcean:setPositionX(0);

        -- 海洋节点回到移动前的位置
        oceanNode:setPositionX(0);

        -- 目标出现效果
        local function callback()
            -- 效果结束回调中把透明蒙版隐藏起来，恢复点击
            local formMask = findChildByName(self.node, "form_mask");
            formMask:setVisible(false);
        end

        -- 外景震动
        local moveBy1 = cc.MoveBy:create(0.05, cc.p(direction * 5, 0));
        local moveBy2 = cc.MoveBy:create(0.05, cc.p(direction * -5, 0));
        local delayTime = cc.DelayTime:create(0.1);
        local callFunc = cc.CallFunc:create(function ()
            -- 冲向浮岛特效
            self:playForwardEffect(curSelect, callback);
        end);

        background:runAction(cc.Sequence:create(moveBy1, moveBy2, delayTime, callFunc));
    end

    local moveBy = cc.EaseInOut:create(cc.MoveBy:create(delay, cc.p(960 * MOVE_OCEAN_NUM * direction, 0)), 2.5);
    local callFunc = cc.CallFunc:create(resetOceanNode);

    oceanNode:runAction(cc.Sequence:create(moveBy, callFunc));
end

-- 屏幕风景移动时，天空移动效果
function UISkyCity:playSkyMoveEffect(direction, delay)
    -- 天空移动（较慢）
    local skyNode = findChildByName(self.node, "CT/background/sky_node");

    -- 创建多个天空
    local skyNum = 1;
    for index = 1, skyNum do
        local skyId = math.random(SKY_TEMPLATE_NUM);
        local node = cc.CSLoader:createNode(string.format("layout/sky_city/background/background_sky_%d.csb", skyId));
        node.id = skyId;
        node:setName("sky" .. index + 1);
        node:setPositionX(-960 * index * direction);
        node:setScale(0.6);
        skyNode:addChild(node);
    end

    local function resetSkyNode()
        local lastSky = skyNode:getChildByName("sky" .. skyNum + 1);

        -- 把前面的sky都干掉，只保留最后一个
        for index = 1, skyNum do
            skyNode:removeChildByName("sky" .. index);
        end

        -- 最后一个节点偷偷变成第一个节点，并且修改x轴坐标
        lastSky:setName("sky1");
        lastSky:setPositionX(0);

        local scaleTo2 = cc.ScaleTo:create(0.5, 1);
        lastSky:runAction(cc.Sequence:create(scaleTo2));

        -- 海洋节点回到移动前的位置
        skyNode:setPositionX(0);
    end

    local moveBy = cc.EaseInOut:create(cc.MoveBy:create(delay, cc.p(960 * direction, 0)), 2.5);

    local delayTime = cc.DelayTime:create(0.2);
    local callFunc = cc.CallFunc:create(resetSkyNode);
    AudioM.playFx("lens_rotation");
    skyNode:runAction(cc.Sequence:create(moveBy, delayTime, callFunc));
end

-- 创建要飞出屏幕外的浮云
function UISkyCity:createForwardCloud(direction, delay)
    -- 随机在天空中生成一些浮云，然后飞出屏幕外
    local backgroundNode = findChildByName(self.node, "CT/background");
    local airShip = findChildByName(self.node, "CT/air_ship");
    local shipPos = cc.p(airShip:getPosition());

    -- 如果列表里面已经有值了，那么说明之前的图片还没析构掉。
    -- 先析构掉旧的浮云
    if type(self.forwardCloudList) == "table" and #self.forwardCloudList > 0 then
        for _, cloudNode in ipairs(self.forwardCloudList) do
            cloudNode:stopAllActions();
            cloudNode:removeFromParent();
        end
    end

    -- 置空
    self.forwardCloudList = {};

    local function createCloud()
        -- 生成一个浮云，飞出屏幕外
        local image = ccui.ImageView:create(getSkyCityPath("sky/floating_cloud" .. math.random(2)), 1);
        local offsetX = math.random(-100, 100);
        local offsetY = math.random(-offsetX, offsetX);
        image.offsetX = offsetX;
        image.offsetY = offsetY;
        image:setScale(math.abs(offsetX) / 100 * 300 / 1000);
        image:setPosition(airShip.initX + direction * MOVE_OCEAN_NUM * -960 + offsetX, shipPos.y + offsetY);
        backgroundNode:addChild(image);

        table.insert(self.forwardCloudList, image);

        local moveBy = cc.EaseInOut:create(cc.MoveBy:create(delay, cc.p(960 * MOVE_OCEAN_NUM * direction, 0)), 2.5);
        image:runAction(cc.Sequence:create(moveBy));
    end

    if math.random(100) < 100 then
        local num = math.random(4, 6);
        for index = 1, num do
            createCloud();
        end
    end
end

-- 屏幕风景移动
function UISkyCity:sceneryMove(delay, curSelect, lastSelected, isNew)
    -- 如果是点击了当前目标，那么不需要移动
    if curSelect == lastSelected and not isNew then
        return;
    end

        -- 准心节点
    local reticleNode = findChildByName(self.node, "CT/reticle_node");
    -- 飞船信息节点
    local airShipInfoNode = findChildByName(self.node, "CT/airship_info");
    -- 飞船按钮节点
    local airShipBtn = findChildByName(self.node, "CT/airship_btn");

    -- 目标先隐藏起来，等下再出现
    reticleNode:setVisible(false);
    airShipInfoNode:setVisible(false);
    airShipBtn:setVisible(false);

    -- 界面上的透明蒙版显示，阻止玩家点击
    local formMask = findChildByName(self.node, "form_mask");
    formMask:setVisible(true);

    -- 比较下之前的目标和当前目标来决定是向左移动还是向右移动
    local newShowXY = SkyExploreM.parsePos(curSelect);
    local lastShowXY = SkyExploreM.parsePos(lastSelected);

    local direction = 1;

    if newShowXY.x > lastShowXY.x then
        direction = -1;
    end

    -- 飞艇移动效果
    self:playShipMoveEffect(curSelect, direction, delay);

    -- 海面移动效果
    self:playOceanMoveEffect(curSelect, direction, delay);

    -- 天空移动效果
    self:playSkyMoveEffect(direction, delay);

    -- 海面移动的时候，热气球和云也要跟着移动
    local floatingCloud = findChildByName(self.node, "CT/floating_cloud");
    local ballIcon = findChildByName(self.node, "CT/fire_balloon");

    local moveBy1 = cc.EaseInOut:create(cc.MoveBy:create(delay, cc.p(960 * MOVE_OCEAN_NUM * direction, 0)), 2.5);
    local callFunc = cc.CallFunc:create(function ()
        floatingCloud:removeAllChildren();
        floatingCloud:setPosition(0, 210);
    end);

    floatingCloud:runAction(cc.Sequence:create(moveBy1, callFunc));

    local moveBy2 = cc.EaseInOut:create(cc.MoveBy:create(delay, cc.p(960 * MOVE_OCEAN_NUM * direction, 0)), 2.5);
    local callFunc = cc.CallFunc:create(function ()
        ballIcon:setVisible(false);
    end);

    ballIcon:runAction(cc.Sequence:create(moveBy2, callFunc));

    -- 提前创建要飞出屏幕外的浮云
    self:createForwardCloud(direction, delay);
end

-- 转方向盘
function UISkyCity:turnSteeringWheel(index, delay, isNew)
    local newShowXY = SkyExploreM.parsePos(index);
    local lastShowXY = self.lastShowXY;
    local angle = 0;

    if newShowXY.x == lastShowXY.x and newShowXY.y <= lastShowXY.y and not isNew then
        -- 往前直行
        angle = 0;
    elseif newShowXY.x > lastShowXY.x and newShowXY.y <= lastShowXY.y then
        -- 右前方
        angle = 180;
    elseif newShowXY.x < lastShowXY.x and newShowXY.y <= lastShowXY.y then
        -- 左前方
        angle = -180;
    elseif newShowXY.x < lastShowXY.x and newShowXY.y >= lastShowXY.y then
        -- 左后方
        angle = -360;
    else
        -- 右后方
        angle = 360;
    end

    self.lastShowXY = newShowXY;

    local node = findChildByName(self.node, "Screen/steering_wheel");
    local rotate = cc.RotateBy:create(delay, angle);
    node:stopAllActions();
    node:setRotation(0);
    node:runAction(rotate);
end

-- 播放云层漂浮效果
function UISkyCity:playFloatingCloudEffect(now)
    local floatingCloud = findChildByName(self.node, "CT/floating_cloud");

    if self.isStopAllEffects then
        return;
    end

    local function floatingCloudEffect(isNow)
        if self.isStopAllEffects then
            return;
        end

        local skyNode = findChildByName(self.node, "CT/sky_bg");

        local height = skyNode:getContentSize().height;

        -- 随机出1或2朵云
        local cloudNum = math.random(2);

        for i = 1, cloudNum do
            local cloudImg = ccui.ImageView:create();
            cloudImg:loadTexture(getSkyCityPath("sky/floating_cloud" .. math.random(2)), 1);

            -- 随机初始个坐标
            local posX = AlignM.frameSize.width / 2 + 100;

            if isNow then
                posX = math.random(AlignM.frameSize.width / 4);
            end

            local i = 1;
            -- 随机抽取飞行方向
            if math.random(100) <= 50 then
                i = -1;
            end

            cloudImg:setPosition(cc.p(i * posX, math.random(-150, 150)));
            floatingCloud:addChild(cloudImg);

            -- 沿抛物线往上飞
            local jumpOut = cc.JumpBy:create(math.random(30, 50), cc.p(i * 1200, 0), 0, 1);
            local callFunc = cc.CallFunc:create(function ()
                cloudImg:removeFromParent();
            end);
            cloudImg:runAction(cc.Sequence:create(jumpOut, callFunc));
        end

        -- 等一段时间后再来一次
        if not isNow then
            performWithDelay(floatingCloud, floatingCloudEffect, math.random(120, 300));
        end
    end

    if not floatingCloud.isForward then
        floatingCloud:stopAllActions();
        performWithDelay(floatingCloud, floatingCloudEffect, math.random(60, 300));
    end

    if now then
        if math.random(100) > 25 then
            floatingCloudEffect(true);
        end
    end
end

-- 播放热气球升空效果
function UISkyCity:playFireBalloonEffect()
    local ballIcon = findChildByName(self.node, "CT/fire_balloon");

    if self.isStopAllEffects then
        return;
    end

    local function fireBalloonEffect()
        local skyNode = findChildByName(self.node, "CT/sky_bg")

        local height = skyNode:getContentSize().height;

        -- 先暂停所有位置
        ballIcon:stopAllActions();

        -- 随机初始个坐标
        ballIcon:setPosition(cc.p(-400, math.random(50, 220)));
        ballIcon:setRotation(0);
        ballIcon:setVisible(true);

        local i = 1;
        -- 随机抽取气球飞行方向
        if math.random(100) <= 50 then
            i = -1;
            ballIcon:setPosition(cc.p(400, math.random(50, 220)));
        end

        -- 沿抛物线往上飞
        local jumpOut = cc.JumpTo:create(50, cc.p(ballIcon:getPositionX() + i * 600, height), 0, 1);
        -- 摇晃、等待
        local rotateTo1 = cc.RotateBy:create(1, 20);
        local rotateTo2 = cc.RotateBy:create(2, -40);
        local rotateBack = cc.RotateBy:create(1, 20);
        local delay = cc.DelayTime:create(2);
        local actionList = cc.RepeatForever:create(cc.Sequence:create(delay, rotateTo1, rotateTo2, rotateBack));

        -- 由于未知原因，ballIcon:runAction(cc.Spawn:create(jumpOut, actionList)); 将导致摇晃不起效，因此分成2个runAction执行
        ballIcon:runAction(cc.Sequence:create(jumpOut));
        ballIcon:runAction(actionList);

        -- 等一段时间后再来一次
        performWithDelay(ballIcon, fireBalloonEffect, math.random(120, 300));
    end

    performWithDelay(ballIcon, fireBalloonEffect, math.random(60, 300));
end

-- 播放雷达重置光效
function UISkyCity:playRadarResetEffect()
    local node = findChildByName(self.node, "Screen/screen_effect");
    local width = self.screen:getContentSize().width;
    local height = self.screen:getContentSize().height;

    if self.isStopAllEffects or self.isStartUp then
        return;
    end

    node:stopAllActions();
    node:removeAllChildren();

    self.screen:setVisible(false);
    self.isStartUp = true;

    local screenBg = findChildByName(self.node, "Screen/screen_bg");
    screenBg:loadTexture(getSkyCityPath("screen_bg"), 1);

    -- 停止所有的特效
    self:stopAllEffects();

    -- 界面上的透明蒙版显示，阻止玩家点击
    local formMask = findChildByName(self.node, "form_mask");
    formMask:setVisible(true);

    local function frameEventFunc(frame)
        if frame.getEvent and frame:getEvent() == "pilor" then
            local index = frame:getFrameIndex();

            -- 开机动画结束后
            if index == 91 then
                self.isStartUp = false;
                self.screen:setVisible(true);
                formMask:setVisible(false);

                self.screen:setOpacity(0);
                local fadeIn = cc.FadeIn:create(1.5);

                self.screen:runAction(cc.Sequence:create(fadeIn));
                -- 开始其他的屏幕效果
                performWithDelay(self, function ()
                    -- 恢复特效
                    self:recoverAllEffects();
                end, 1);

            elseif index < 91 then
                self.screen:setVisible(false);
            end
        end
    end

    -- 播放雷达重置动画
    playEffect(node, 1805, node:getContentSize().width / 2 + 10, node:getContentSize().height / 2, frameEventFunc);

end

-- 播放雷达启动光效
function UISkyCity:playRadarStartEffect()
    local node = findChildByName(self.node, "Screen/screen_effect");
    local width = self.screen:getContentSize().width;
    local height = self.screen:getContentSize().height;

    if self.isStopAllEffects or self.isStartUp then
        return;
    end

    node:stopAllActions();
    node:removeAllChildren();

    if not self.screenStarEffect then
        self.screenStarEffect = true;
        self.screen:setVisible(false);
        self.isStartUp = true;

        local screenBg = findChildByName(self.node, "Screen/screen_bg");
        screenBg:loadTexture(getSkyCityPath("screen_bg"), 1);

        -- 停止所有的特效
        self:stopAllEffects();

        -- 界面上的透明蒙版显示，阻止玩家点击
        local formMask = findChildByName(self.node, "form_mask");
        formMask:setVisible(true);

        local function frameEventFunc(frame)
            if frame.getEvent and frame:getEvent() == "pilor" then
                local index = frame:getFrameIndex();

                -- 开机动画结束后
                if index == 403 then
                    self.isStartUp = false;
                    self.screen:setVisible(true);
                    formMask:setVisible(false);

                    self.screen:setOpacity(0);
                    local fadeIn = cc.FadeIn:create(1.5);

                    self.screen:runAction(cc.Sequence:create(fadeIn));
                    -- 开始其他的屏幕效果
                    performWithDelay(self, function ()
                        -- 恢复特效
                        self:recoverAllEffects();

                        local btnAddEnergy = findChildByName(self.node, "Screen/energy_node/btn_add_energy");
                        local resetBtn = findChildByName(self.node, "Screen/reset_node/btn_reset");
                        local btnTeam = findChildByName(self.node, "BT/btn_team");
                        local btnShip = findChildByName(self.node, "BT/btn_ship");
                        local btnSkill = findChildByName(self.node, "BT/btn_skill");
                        local btnRepair = findChildByName(self.node, "BT/btn_repair");

                        btnAddEnergy:setVisible(true);
                        resetBtn:setVisible(true);
                        btnTeam:setVisible(true);
                        btnShip:setVisible(true);
                        btnSkill:setVisible(true);
                        btnRepair:setVisible(false);

                        for _, button in ipairs({btnAddEnergy, resetBtn, btnTeam, btnShip, btnSkill}) do
                            button:setScale(0.1);
                            button:setOpacity(0);
                            TextStyleM.setOutlineStyle(button, 1, TextStyleM.TEXT_OUTLINE_COLOR, true);

                            local scaleTo = cc.ScaleTo:create(0.2, 1);
                            local fadeIn = cc.FadeIn:create(0.2);
                            button:runAction(cc.Sequence:create(cc.Spawn:create(scaleTo, fadeIn)));
                        end
                    end, 0.5);

                    -- 播放漫画
                    local function after_talk3()
                        local effectNode = self.node:getChildByName("effect");

                        UIMgr:getCurrentScene().topMenu:setVisible(false);

                        local function callback()
                            NewAirShipM.setGuideProgress(5);
                        end

                        -- 前奏震动
                        playShakeEffect(self, 0.03, 4, 12);
                        AudioM.playFx("cartoon_prelude");

                        playCartoonEffect(1, callback);
                        performWithDelay(self, function ()
                            self:redrawAirShip();
                        end, 2);
                    end

                    -- 舰长对白
                    startDialogueTemplate("sky_city_talk3", after_talk3);
                elseif index < 403 then
                    self.screen:setVisible(false);
                end
            end
        end

        -- 播放雷达启动动画
        playEffect(node, 1803, node:getContentSize().width / 2 + 10, node:getContentSize().height / 2, frameEventFunc);

        return;
    end
end

-- 播放屏幕的效果
function UISkyCity:playScreenEffects()
    local node = findChildByName(self.node, "Screen/screen_effect");
    local width = self.screen:getContentSize().width;
    local height = self.screen:getContentSize().height;

    if self.isStopAllEffects or self.isStartUp then
        return;
    end

    node:stopAllActions();
    node:removeAllChildren();

    if not self.screenStarEffect and NewAirShipM.isFinishGuide() then
        self.screenStarEffect = true;
        self.screen:setVisible(false);
        self.isStartUp = true;

        local screenBg = findChildByName(self.node, "Screen/screen_bg");
        screenBg:loadTexture(getSkyCityPath("screen_bg"), 1);

        -- 停止所有的特效
        self:stopAllEffects();

        -- 界面上的透明蒙版显示，阻止玩家点击
        local formMask = findChildByName(self.node, "form_mask");
        formMask:setVisible(true);

        local function frameEventFunc(frame)
            if frame.getEvent and frame:getEvent() == "pilor" then
                local index = frame:getFrameIndex();

                -- 开机动画结束后
                if index == 7 then
                    self.isStartUp = false;
                    self.screen:setVisible(true);
                    formMask:setVisible(false);

                    self.screen:setOpacity(0);
                    local fadeIn = cc.FadeIn:create(1.5);

                    self.screen:runAction(cc.Sequence:create(fadeIn));
                    -- 开始其他的屏幕效果
                    performWithDelay(self, function ()
                        -- 恢复特效
                        self:recoverAllEffects();
                    end, 1);

                    -- 自动滚动屏幕到有目标的区域
                    self:autoScroll();

                elseif index < 7 then
                    self.screen:setVisible(false);
                end
            end
        end

        -- 播放闪烁动画
        playEffect(node, 1806, node:getContentSize().width / 2 + 10, node:getContentSize().height / 2, frameEventFunc);

        return;
    end

    -- 扫屏光效
    local function scanEffect()
        if self.isStopAllEffects or self.isStartUp then
            return;
        end

        -- 找出或者创建扫屏效果用的图片1
        local scanEffectImg1 = node:getChildByName("scan_effect_1");
        if not scanEffectImg1 then
            scanEffectImg1 = ccui.ImageView:create();
            -- 如果是从天空副本回到飞艇界面，那么先缓存一下天空之城的合图资源
            -- 其它情况下，合图资源走cocos引擎的自调度
            if DungeonM.dungeonType == DUNGEON_TYPE_SKY_INSTANCE or
                DungeonM.dungeonType == DUNGEON_TYPE_SKY_ACTIVITY_INSTANCE then
                cc.SpriteFrameCache:getInstance():addSpriteFrames(SKY_CITY_RES_PATH);
            end
            scanEffectImg1:loadTexture(getSkyCityPath("scan_effect"), 1);
            scanEffectImg1:setScale(1.5);
            scanEffectImg1:setName("scan_effect_1");
            node:addChild(scanEffectImg1);
        end

        -- 初始化坐标
        scanEffectImg1:setPosition(cc.p(width * 1.5, height / 2));

        -- 找出或者创建扫屏效果用的图片2
        local scanEffectImg2 = node:getChildByName("scan_effect_2");
        if not scanEffectImg2 then
            scanEffectImg2 = ccui.ImageView:create();
            scanEffectImg2:loadTexture(getSkyCityPath("scan_effect"), 1);
            scanEffectImg2:setScale(-1.5);
            scanEffectImg2:setName("scan_effect_2");
            node:addChild(scanEffectImg2);
        end

        -- 初始化坐标
        scanEffectImg2:setPosition(cc.p(-0.5 * width, height / 2));

        -- 图片移动
        local moveTo1 = cc.MoveBy:create(4, cc.p(-2 * width, 0));
        local moveTo2 = cc.MoveBy:create(4, cc.p(2 * width, 0));
        scanEffectImg1:runAction(cc.Sequence:create(moveTo1));
        scanEffectImg2:runAction(cc.Sequence:create(moveTo2));

        -- 5秒后再来一次
        performWithDelay(node, scanEffect, 5);
    end
    scanEffect();

    -- 边缘2侧的光效
    local function borderEffect()
        -- 左侧飞入光效
        playEffect(node, 1800, 15, height / 4);
        -- 右侧飞入光效
        local _, effect = playEffect(node, 1800, width - 15, height * 3 / 4);
        local scale = effect:getScaleY();
        effect:setScaleY(-1 * scale);

        -- 3秒后再来一次
        performWithDelay(node, borderEffect, 3);
    end
    performWithDelay(node, borderEffect, 1);
end

-- 播放准心的特效
function UISkyCity:playReticleEffect()
    local reticleNode = findChildByName(self.node, "CT/reticle_node");
    local reticleNode1 = findChildByName(reticleNode, "reticle1");
    local reticleNode2 = findChildByName(reticleNode, "reticle2");
    local reticleNode3 = findChildByName(reticleNode, "reticle3");
    local reticleNode4 = findChildByName(reticleNode, "reticle4");
    local reticleNode5 = findChildByName(reticleNode, "reticle5");

    -- 先暂停下所有准心图片的行为
    reticleNode:stopAllActions();
    reticleNode1:stopAllActions();
    reticleNode2:stopAllActions();
    reticleNode3:stopAllActions();
    reticleNode4:stopAllActions();
    reticleNode5:stopAllActions();

    -- 如果正在播光效，那就先把光效移除掉
    if reticleNode.effectNode then
        reticleNode:removeChild(reticleNode.effectNode);
        reticleNode.effectNode = nil;
    end

    reticleNode:setVisible(true);
    reticleNode1:setVisible(false);
    reticleNode2:setVisible(false);
    reticleNode3:setVisible(false);
    reticleNode4:setVisible(false);
    reticleNode5:setVisible(false);

    local function frameEventFunc(frame)
        if frame.getEvent and frame:getEvent() == "pilor" then
            local index = frame:getFrameIndex();

            if index == 32 then
                reticleNode.effectNode:setVisible(false);
                reticleNode1:setVisible(true);
                reticleNode2:setVisible(true);
                reticleNode3:setVisible(true);
                reticleNode4:setVisible(true);
                reticleNode5:setVisible(true);
                reticleNode1:setScale(1);
                reticleNode2:setScale(1);
                reticleNode3:setRotation(0);
                reticleNode4:setRotation(0);
                reticleNode5:setScale(1);

                -- 缩小后放大
                local delay1 = cc.DelayTime:create(0.4);
                local scaleDown1 = cc.ScaleTo:create(0.7, 0.7);
                local scaleUp1 = cc.ScaleTo:create(0.7, 1);

                -- 放大后缩小
                local delay2 = cc.DelayTime:create(0.4);
                local scaleUp2 = cc.ScaleTo:create(0.7, 1.3);
                local scaleDown2 = cc.ScaleTo:create(0.7, 1);

                -- 右转后左转
                local delay3 = cc.DelayTime:create(0.4);
                local rotateRight3 = cc.RotateBy:create(2, 90);
                local rotateLeft3 = cc.RotateBy:create(2, -90);

                -- 左转后右转
                local delay4 = cc.DelayTime:create(0.4);
                local rotateLeft4 = cc.RotateBy:create(2, -90);
                local rotateRight4 = cc.RotateBy:create(2, 90);

                -- 小幅度的放大后缩小
                local delay5 = cc.DelayTime:create(0.4);
                local scaleUp5 = cc.ScaleTo:create(1, 1.05);
                local scaleDown5 = cc.ScaleTo:create(1, 1);

                reticleNode1:runAction(cc.RepeatForever:create(cc.Sequence:create(delay1, scaleDown1, scaleUp1)));
                reticleNode2:runAction(cc.RepeatForever:create(cc.Sequence:create(delay2, scaleUp2, scaleDown2)));
                reticleNode3:runAction(cc.RepeatForever:create(cc.Sequence:create(delay3, rotateRight3, rotateLeft3)));
                reticleNode4:runAction(cc.RepeatForever:create(cc.Sequence:create(delay4, rotateLeft4, rotateRight4)));
                reticleNode5:runAction(cc.RepeatForever:create(cc.Sequence:create(delay5, scaleUp5, scaleDown5)));
            end
        end
    end

    local _, effectNode = playEffect(reticleNode, 1802, 0, 0, frameEventFunc);
    effectNode:setVisible(true);
    reticleNode.effectNode = effectNode;
end

-- 停止所有的表现
function UISkyCity:stopAllEffects()
    self.isStopAllEffects = true;

    -- 暂停电子屏幕的表现
    local node = findChildByName(self.node, "Screen/screen_effect");
    node:stopAllActions();
    node:removeAllChildren();

    -- 暂停探索按钮提示光效
    local selectNode = findChildByName(self.node, "Screen/select_node");
    selectNode:stopAllActions();

    if selectNode.effectNode then
        selectNode:removeChild(selectNode.effectNode);
    end

    -- 停止雷达点的跳动
    local iconImg = findChildByName(self.node, "Screen/radar_info/icon");
    iconImg:stopAllActions();

    -- 停止云层漂浮的控制器
    local floatingCloud = findChildByName(self.node, "CT/floating_cloud");
    floatingCloud:stopAllActions();
end

-- 恢复所有的表现
function UISkyCity:recoverAllEffects()
    self.isStopAllEffects = false;

    -- 开始电子屏幕的效果表现
    self:playScreenEffects();

    -- 探索按钮光效
    self:checkSelectBtnTipEffect();

    -- 雷达点的效果
    self:redrawRadarInfo();

    -- 开始云层漂浮的控制器
    self:playFloatingCloudEffect();
end

-- 打开功能浮岛
function UISkyCity:openUsageIsland(id)
    if id == SCRAP_BONUS then
        -- 雕像奖励
        require "game/ui/form/sky_city/UISkyScrapBonus";
        closeFormByName("UISkyScrapBonus");
        local bonusForm = UISkyScrapBonus.create();
        UIMgr.getCurrentScene():addForm(bonusForm);
        AudioM.playFx("button_award");
    elseif id == SKY_CAMP then
        -- 打开天空营地
        require "game/ui/form/shop/UIPetShop";
        UIMgr.getCurrentScene():removeFormByName("UISkyPetShop");
        local uiSkyCityShipSkill = UIPetShop.create(nil, "SkyPetShop");
        UIMgr.getCurrentScene():addForm(uiSkyCityShipSkill);
        AudioM.playFx("dup_shop");
    elseif id == SKY_SHOP then
        -- 打开商城界面
        require "game/ui/form/sky_city/UISkyShop";
        UIMgr.getCurrentScene():removeFormByName("UISkyShop");
        local uiForm = UISkyShop.create();
        UIMgr.getCurrentScene():addForm(uiForm);
        AudioM.playFx("dup_shop");
    elseif id == SKY_TOWER then
        -- 打开试练塔界面
        require "game/ui/form/sky_city/UISkyCityTower";
        UIMgr.getCurrentScene():removeFormByName("UISkyCityTower");
        local camp = ME.user.dbase:query("activedSkyGroup");
        local uiForm = UISkyCityTower.create(camp);
        UIMgr.getCurrentScene():addForm(uiForm);
        AudioM.playFx("dup_shop");
    end
end

-- 重绘功能浮岛的按钮
function UISkyCity:redrawUsageIslandBtn(id)
    local btnDoEvent = findChildByName(self.node, "CT/airship_btn/do_event/btn_do_event");
    local btnInfo = findChildByName(self.node, "CT/airship_btn/info/btn_info");
    local infoNode = findChildByName(self.node, "CT/airship_btn/info");

    if id == SCRAP_BONUS then
        btnDoEvent:setTitleText(getLocStr("btn_text_bonus"));
    elseif id == SKY_CAMP then
        btnDoEvent:setTitleText(getLocStr("btn_text_recruit2"));
        btnInfo:setTitleText(getLocStr("btn_text_exchange"));

        btnInfo:setVisible(true);
        infoNode:setVisible(true);
    elseif id == SKY_SHOP then
        btnDoEvent:setTitleText(getLocStr("btn_text_buy"));
    elseif id == SKY_TOWER then
        btnDoEvent:setTitleText(getLocStr("btn_text_trial"));
        btnInfo:setTitleText(getLocStr("btn_text_info"));

        btnInfo:setVisible(true);
        infoNode:setVisible(true);
    end
end

-- 检查屏幕内是否有浮岛
function UISkyCity:isEventInScreen()
    local viewWidth     = self.screen:getContentSize().width;
    local viewHeight    = self.screen:getContentSize().height;
    local lastPx        = self.screen:getInnerContainer():getPositionX();
    local lastPy        = self.screen:getInnerContainer():getPositionY();

    -- 计算屏幕显示范围的最大最小x,y
    local minX = -1 * lastPx;
    local maxX = minX + viewWidth;
    local minY = -1 * lastPy;
    local maxY = minY + viewHeight;

    -- 遍历所有目标，判断是否有目标在x,y的范围内
    local ret = 0;
    for _, itemName in ipairs(self.eventIconList) do
        local item = self.screen:getChildByName(itemName);
        local posX = item:getPositionX();
        local posY = item:getPositionY();

        if posX > minX and posX < maxX and posY > minY and posY < maxY then
            ret = 1;
            break;
        end
    end

    return ret;
end

-- 自动滚动屏幕至显示目标浮岛
function UISkyCity:autoScroll()
    if self:isEventInScreen() == 1 or #table.keys(self.eventsList) <= 0 then
        -- 当前屏幕里还有目标或者雷达区域内已经没有任何目标了
        print("当前屏幕里还有目标或者雷达区域内已经没有任何目标了");
        return;
    end

    -- 自动移动到最近的浮岛
    self:autoScrollToNearlyEvent();
end

-- 自动移动到最近的浮岛
function UISkyCity:autoScrollToNearlyEvent()
    local viewWidth     = self.screen:getContentSize().width;
    local viewHeight    = self.screen:getContentSize().height;
    local innerWidth    = self.screen:getInnerContainerSize().width;
    local innerHeight   = self.screen:getInnerContainerSize().height;
    local lastPx        = self.screen:getInnerContainer():getPositionX();
    local lastPy        = self.screen:getInnerContainer():getPositionY();

    -- 计算屏幕中心点的坐标
    local centerPosX = viewWidth / 2 - lastPx;
    local centerPosY = viewHeight / 2 - lastPy;

    -- 获取一个离屏幕中心点最近的目标
    local ret;
    local minDistance = innerWidth;

    for _, itemName in ipairs(self.eventIconList) do
        local item = self.screen:getChildByName(itemName);
        local posX = item:getPositionX();
        local posY = item:getPositionY();

        local curDistance = math.max(math.abs(posX - centerPosX), math.abs(posY - centerPosY));

        if curDistance < minDistance then
            minDistance = curDistance;
            ret = item;
        end
    end

    if not ret then
        return;
    end

    -- 滚动屏幕到最近的目标
    local posX = ret:getPositionX();
    local posY = ret:getPositionY();
    local destinationX = viewWidth / 2 - posX;
    local destinationY = viewHeight / 2 - posY;

    -- 移动的时候横坐标不要露出迷雾
    local max = 0;
    local min = viewWidth - innerWidth;
    if destinationX > max then
        destinationX = max;
    end
    if destinationX < min then
        destinationX = min;
    end

    -- 直线移动
    local moveTo = cc.MoveTo:create(0.7, cc.p(destinationX, destinationY));

    self.screen:getInnerContainer():stopAllActions();
    self.screen:getInnerContainer():runAction(moveTo);
end

-- 播放神殿奖励特效
function UISkyCity:playSkyCityBonusEffect(bonusList, fromPos, needTip)
    local effectNode = findChildByName(self.node, "effect");
    local btnShip = findChildByName(self.node, "BT/btn_ship");
    local btnSkill = findChildByName(self.node, "BT/btn_skill");
    local btnStore = findChildByName(self.node, "BT/btn_store");

    -- 遍历奖励列表，天空之城相关的奖励飞入对应的按钮
    local function bonusFlyEffect(index)
        if index > #bonusList then
            return;
        end

        local toPos;
        local showTip = false;
        local bonusInfo = bonusList[index];
        local icon = ccui.ImageView:create();

        if bonusInfo[1] == 1 then
            -- 道具奖励
            if SkyResourceM.query(bonusInfo[2]) then
                -- 天空之城道具
                icon:loadTexture(getItemIconPath(ItemM.query(bonusInfo[2], "icon")));
                local worldPos = btnStore:convertToWorldSpaceAR(cc.p(0, 0));
                toPos = effectNode:convertToNodeSpace(worldPos);

                if SkyResourceM.query(bonusInfo[2], "alias") == "captain_dairy" then
                    -- 舰长的日记
                    -- 提示 "你发现了XXX"
                    showTip = true;
                end
            elseif ItemM.query(bonusInfo[2], "type") == ITEM_TYPE_DRAWING then
                -- 飞艇碎片
                icon:loadTexture(getItemIconPath(ItemM.query(bonusInfo[2], "icon")));
                local worldPos = btnShip:convertToWorldSpaceAR(cc.p(0, 0));
                toPos = effectNode:convertToNodeSpace(worldPos);
            end

            -- tipStr = string.format(getLocStr("sky_city_bonus_msg"), ItemM.query(bonusInfo[2], "name"));
            tipStr = ItemM.query(bonusInfo[2], "name");
        elseif bonusInfo[1] == 6 then
            -- 秘宝奖励
            icon:loadTexture(getAirShipSkillIconPath(SkyShipSkillM.query(bonusInfo[2], "icon")));
            local worldPos = btnSkill:convertToWorldSpaceAR(cc.p(0, 0));
            toPos = effectNode:convertToNodeSpace(worldPos);

            -- 提示 "你发现了XXX"
            --tipStr = string.format(getLocStr("sky_city_bonus_msg"), SkyShipSkillM.query(bonusInfo[2], "name"));
            tipStr = SkyShipSkillM.query(bonusInfo[2], "name");
            showTip = true;
        elseif bonusInfo[1] == 8 then
            -- 机器人奖励
            icon:loadTexture(getRobotIconPath(SkyRobotM.query(bonusInfo[2], "icon")));
            local worldPos = btnStore:convertToWorldSpaceAR(cc.p(0, 0));
            toPos = effectNode:convertToNodeSpace(worldPos);

            -- 提示 "你发现了XXX"
            --tipStr = string.format(getLocStr("sky_city_bonus_msg"), SkyRobotM.query(bonusInfo[2], "name"));
            tipStr = SkyRobotM.query(bonusInfo[2], "name");
            showTip = true;
        end

        if needTip or showTip then
            if tipStr then
                --alert(tipStr);
                alert(string.format(getLocStr("bonus_item_desc"), tipStr, bonusInfo[3]));
            end
        end

        if toPos then
            if fromPos then
                icon:setPosition(fromPos);
            end
            icon:setOpacity(200);
            effectNode:addChild(icon);

            local jumpTo = cc.JumpTo:create(0.5, toPos, 0, 1);
            local rotate = cc.RotateBy:create(0.5, 180);
            local scaleTo = cc.ScaleTo:create(0.5, 0.5);
            local callFunc = cc.CallFunc:create(function ()
                icon:removeFromParent();

                playEffect(effectNode, 1813, toPos.x, toPos.y);
            end);

            icon:runAction(cc.Sequence:create(cc.Spawn:create(rotate, jumpTo, scaleTo), callFunc));

            performWithDelay(effectNode, function ()
                bonusFlyEffect(index + 1);
            end, 0.4)
        else
            bonusFlyEffect(index + 1);
        end
    end

    bonusFlyEffect(1);
end

-- 播放目标浮岛/飞艇按钮出现特效
function UISkyCity:playAirshipBtnEffect(button)
    button:setScale(0.1);
    button:setOpacity(0);

    local delay     = cc.DelayTime:create(0.2);
    local fadeIn    = cc.FadeIn:create(0.3);
    local scaleUp1  = cc.ScaleTo:create(0.3, 1.2);
    local scaleDown = cc.ScaleTo:create(0.1, 0.95);
    local scaleUp2  = cc.ScaleTo:create(0.05, 1);

    button:runAction(cc.Sequence:create(delay, cc.Spawn:create(fadeIn, scaleUp1), scaleDown, scaleUp2));
end

-- 冲向浮岛特效
function UISkyCity:playForwardEffect(curSelect, callback)
    local airShip = findChildByName(self.node, "CT/air_ship");
    local floatingCloud = findChildByName(self.node, "CT/floating_cloud");

    -- 先绘制下浮云
    floatingCloud:setScale(0.01);
    self:playFloatingCloudEffect(true)

    -- 速度线效果
    self:playSpeedLineEffect();

    -- 播放海面前进效果
    self:playForwardOceanEffect(curSelect);

    -- 播放天空前进效果
    self:playForwardSkyEffect();

    -- 目标飞艇/浮岛放大
    local scaleTo1 = cc.EaseIn:create(cc.ScaleTo:create(0.5, 1.1), 2.5);
    local fadeIn = cc.CallFunc:create(function()

        local airShipEffectNode = findChildByName(airShip, "airship");

        if airShipEffectNode then
            setOpacity(airShipEffectNode, 255);
        else
            local childs = getAllChilds(airShip);
            for _, c in pairs(childs) do
                c:runAction(cc.Sequence:create(cc.FadeIn:create(0.5)));
            end
        end
    end);
    local spawn = cc.Spawn:create(scaleTo1, fadeIn);
    local scaleTo2 = cc.ScaleTo:create(0.2, 0.95);
    local scaleTo3 = cc.ScaleTo:create(0.1, 1);
    local callfunc1 = cc.CallFunc:create(function()
        self:redrawAirShip(true, nil, true);
    end);
    local callfunc2 = cc.CallFunc:create(function()
        callback();
    end);

    airShip:runAction(cc.Sequence:create(spawn, callfunc1, scaleTo2, scaleTo3, callfunc2));

    -- 浮云放大
    floatingCloud.isForward = true;
    local scaleTo4 = cc.EaseIn:create(cc.ScaleTo:create(0.5, 1.1), 2.5);
    local scaleTo5 = cc.ScaleTo:create(0.2, 0.95);
    local scaleTo6 = cc.ScaleTo:create(0.1, 1);
    local callfunc = cc.CallFunc:create(function()
        floatingCloud.isForward = nil;
    end);
    floatingCloud:runAction(cc.Sequence:create(scaleTo4, scaleTo5, scaleTo6, callfunc));
end

-- 速度线效果
function UISkyCity:playSpeedLineEffect()
    local speedLine = findChildByName(self.node, "CT/background/speed_line");
    speedLine:setVisible(true);

    -- 调用函数切换速度线资源
    local function changeLine(index)

        local iconPath = string.format("backgroud/speed_line" .. index % 2);
        if not cc.SpriteFrameCache:getInstance():getSpriteFrame(iconPath) then
            cc.SpriteFrameCache:getInstance():addSpriteFrames(SKY_CITY_BACKGROUND_PATH);
        end

        speedLine:loadTexture(getSkyCityPath(iconPath), 1);

        if index > 8 then
            speedLine:setVisible(false);
            return;
        end

        -- 每0.05秒切换一次速度线图片
        performWithDelay(speedLine, function ()
            changeLine(index + 1);
        end, 0.05);
    end

    changeLine(1);
end

-- 播放海面前进效果
function UISkyCity:playForwardOceanEffect(curSelect)
    local airShip = findChildByName(self.node, "CT/air_ship");
    local backgroundNode = findChildByName(self.node, "CT/background");
    local oceanNode = findChildByName(self.node, "CT/background/ocean_node");

    local eventInfo = self.eventsList[curSelect];

    -- 判断下是否有浮岛信息
    if not eventInfo then
        return;
    end

    local oceanNum = 2;
    local scale = 100;
    -- 先创建过渡海面
    for index = 1, oceanNum do
        local node;
        local id = eventInfo["ocean_id"] or 1;

        if eventInfo.id == SKY_TOWER and index == oceanNum then
            id = ME.user.dbase:query("activedSkyGroup");
            node = cc.CSLoader:createNode(string.format("layout/sky_city/background/background_tower_%d.csb", id));

            -- 播放漂浮物的效果
            local ParentNode = findChildByName(node, "Node_1");

            local index = 1;
            repeat
                local flutter = findChildByName(ParentNode, "flutter" .. index);
                if flutter then
                    local delay = math.random(0, 8);
                    performWithDelay(flutter, function ()
                        playFluctuateAnima(flutter, 1.5, 8);
                    end, delay);
                    index = index + 1;
                end
            until flutter == nil;

        else
            if index ~= oceanNum then
                id = math.random(OCEAN_TEMPLATE_NUM);
            end

            node = cc.CSLoader:createNode(string.format("layout/sky_city/background/background_ocean_%d.csb", id));
        end

        node.id = id;
        node:setName("ocean" .. index + 1);
        node:setScale(1 / scale);
        oceanNode:addChild(node);
    end

    -- 播放海面特效
    for index = 1, oceanNum + 1 do
        local node = oceanNode:getChildByName("ocean" .. index);
        if index == oceanNum + 1 then
            -- 最后一个海面放大到正常比例
            local delayTime = cc.DelayTime:create(0.1);
            local scaleTo1 = cc.EaseIn:create(cc.ScaleTo:create(0.5, 1.1), 2.5);
            local scaleTo2 = cc.ScaleTo:create(0.2, 0.95);
            local scaleTo3 = cc.ScaleTo:create(0.1, 1);
            local callfunc1 = cc.CallFunc:create(function()
                playShakeEffect(airShip, 0.03, 3, 2);
                playShakeEffect(backgroundNode, 0.03, 3, 2);
            end);
            local callfunc2 = cc.CallFunc:create(function()
                node:setName("ocean1");
            end);
            node:runAction(cc.Sequence:create(delayTime, scaleTo1, callfunc1, scaleTo2, scaleTo3, callfunc2));
        else
            -- 中间过渡海面边放大边移动
            local scaleTo = cc.ScaleTo:create(0.5, scale * node:getScale());
            local moveBy = cc.MoveBy:create(0.6, cc.p(0, DESIGN_HEIGHT / -4));
            local callfunc = cc.CallFunc:create(function()
                node:removeFromParent();
            end);
            node:runAction(cc.Sequence:create(cc.Spawn:create(scaleTo, moveBy), callfunc));
        end
    end
end

-- 播放天空前进效果
function UISkyCity:playForwardSkyEffect()
    -- 遍历forwardCloudList的浮云浮云，然后飞出屏幕外
    if #self.forwardCloudList > 0 then
        for _, image in ipairs(self.forwardCloudList) do
            local pos;
            local len = 320;
            local offsetX = image.offsetX;
            local offsetY = image.offsetY;
            if math.abs(offsetX) > math.abs(offsetY) then
                pos = cc.p(len / offsetX * math.abs(offsetX), offsetY * math.abs(len / offsetX));
            else
                pos = cc.p(offsetX * math.abs(len / offsetY), len / offsetY * math.abs(offsetY));
            end

            local scaleTo = cc.ScaleTo:create(0.5, math.random(80, 100) / 100);
            local moveBy = cc.MoveBy:create(0.5, pos);
            local callfunc = cc.CallFunc:create(function()
                image:removeFromParent();
            end);

            image:runAction(cc.Sequence:create(cc.Spawn:create(scaleTo, moveBy), callfunc));
        end
    end
    self.forwardCloudList = {};
end

-- 隐藏准心
function UISkyCity:showInfoNode(isVisible)
    -- 先隐藏了准心，目标信息，按钮节点。播放完光效后显示
    local isShow = iif(isVisible ~= true, false, true);

    -- 准心节点
    local reticleNode = findChildByName(self.node, "CT/reticle_node");
    reticleNode:setVisible(isShow);

    -- 飞船信息节点
    local airShipInfoNode = findChildByName(self.node, "CT/airship_info");
    airShipInfoNode:setVisible(isShow);

    -- 飞船按钮节点
    local airShipBtn = findChildByName(self.node, "CT/airship_btn");
    airShipBtn:setVisible(isShow);

    -- 飞船节点
    local airShip = findChildByName(self.node, "CT/air_ship");
    local eventInfo = self.eventsList[self.selectPos];
    if eventInfo and eventInfo.id == SKY_TOWER and isVisible then
        airShip:setVisible(not isShow);
    else
        airShip:setVisible(isShow);
    end
end

-- 尝试播放替换阵营后的效果
function UISkyCity:tryPlaySkyTowerApearEffect()
    if ME.user.dbase:queryTemp("skytower_apear") ~= 1 then
        return;
    end

    ME.user.dbase:deleteTemp("skytower_apear");

    -- 准心节点
    local reticleNode = findChildByName(self.node, "CT/reticle_node");
    reticleNode:setVisible(false);

    -- 飞船信息节点
    local airShipInfoNode = findChildByName(self.node, "CT/airship_info");
    airShipInfoNode:setVisible(false);

    -- 飞船按钮节点
    local airShipBtn = findChildByName(self.node, "CT/airship_btn");
    airShipBtn:setVisible(false);

    local formMask = findChildByName(self.node, "form_mask");
    formMask:setVisible(true);

    -- 目标出现效果
    local function callback()
        -- 效果结束回调中把透明蒙版隐藏起来，恢复点击
        formMask:setVisible(false);
    end


    self:playForwardEffect(self.selectPos, callback);
end

-- 定时检测是否有需要刷新的内容
function UISkyCity:checkUpdate()
    local function update()
        -- 刷新雷达面板浮岛冷却状态
        for pos, eventInfo in pairs(self.eventsList) do
            local iconImg = findChildByName(self.screen, string.format(EVENT_NAME, pos));
            local frameImg = findChildByName(self.screen, string.format(FRAME_NAME, pos));

            if iconImg and frameImg then
                -- 处于冷却状态的话，显示为半透
                self:changeIconState(iconImg, frameImg, self:isPosInCoolTime(eventInfo));
            end
        end
    end

    -- 1秒检查一次
    return cc.Director:getInstance():getScheduler():scheduleScriptFunc(update, 1, false);
end

-- 改变屏幕图标及其外框的是否不可用状态
function UISkyCity:changeIconState(iconImg, frameImg, isDisable)
    if iconImg.isDisable ~= isDisable then
        -- 如果在冷却状态就显示为半透，否则不透明
        if isDisable then
            iconImg:setOpacity(90);
            frameImg:setOpacity(90);
        else
            iconImg:setOpacity(255);
            frameImg:setOpacity(255);
        end

        iconImg.isDisable = isDisable;
    end
end

-- 定时检测屏幕图标的效果是否需要变化
function UISkyCity:checkIconEffect()
    -- 刷新遍历屏幕图标，逐一处理
    for pos, eventInfo in pairs(self.eventsList) do
        local iconImg = findChildByName(self.screen, string.format(EVENT_NAME, pos));

        if iconImg then
            self:checkIconAnimation(iconImg);
        end
    end
end

-- 节点的边框动画
function UISkyCity:frameAnimation(frameIcon, frameImg, rate)
    if not frameImg or not frameIcon then
        return;
    end

    local action;
    local frameAction;
    if frameIcon == "frame_round" then
        -- 圆形边框 -- 顺时针旋转
        action = cc.Sequence:create(cc.RotateBy:create(3*rate, 360));
    elseif frameIcon == "frame_square" then
        -- 方形边框 -- 呼吸
        local scaleDown = cc.ScaleTo:create(0.5*rate, 0.95);
        local scaleUp = cc.ScaleTo:create(0.5*rate, 1);
        local delay = cc.DelayTime:create(0.2*rate);
        action = cc.Sequence:create(scaleDown, scaleUp, delay);
    elseif frameIcon == "frame_hexagon" then
        -- 六边形边框 -- 逆时针旋转
        action = cc.Sequence:create(cc.RotateBy:create(3*rate, -360));
    end

    -- 如果之前有行为，则停止
    if frameImg.frameAction then
        frameImg:stopAction(frameImg.frameAction);
    end

    -- 如果需要行为，则添加
    if action then
        frameAction = frameImg:runAction(cc.RepeatForever:create(action));
        frameImg.frameAction = frameAction;
    end
end

-- 节点的图标动画
function UISkyCity:iconAnimation(screenIcon, iconImg, rate)
    if not iconImg or not iconImg then
        return;
    end

    -- 根据需要播放图标动画
    if self:needIconAnimation(iconImg) then
        if not iconImg.effectIcon then
            local image = ccui.ImageView:create();
            image:loadTexture(getSkyCityIconPath(screenIcon), 1);
            image:setPosition(cc.p(iconImg:getContentSize().width/2, iconImg:getContentSize().height/2))
            image:setLocalZOrder(0);

            iconImg:addChild(image);
            iconImg.effectIcon = image;
        end

        local image = iconImg.effectIcon;

        local duration = 1;
        local initScale = iconImg:getScale();
        local scaleUp = cc.ScaleTo:create(duration, 2 * initScale);
        local fadeOut = cc.FadeTo:create(duration, 0);
        local delay   = cc.DelayTime:create(1);
        local callFunc = cc.CallFunc:create(function()
            image:setVisible(true);
            image:setScale(initScale);
            image:setOpacity(255);
        end);
        image:runAction(cc.RepeatForever:create(cc.Sequence:create(cc.Spawn:create(scaleUp, fadeOut), delay, callFunc)));
    elseif iconImg.effectIcon then
        iconImg.effectIcon:setVisible(false);
        iconImg.effectIcon:stopAllActions();
    end
end

-- 检测图标的动画是否需要变化
function UISkyCity:checkIconAnimation(iconImg)
    local screenIcon = iconImg.icon;

    self:iconAnimation(screenIcon, iconImg, 1);
end

-- 是否需要显示图标闪烁动画
function UISkyCity:needIconAnimation(iconImg)
    local needAnimation = false;
    local screenIcon = iconImg.icon;
    local pos = iconImg.pos;
    local eventInfo = self.eventsList[pos];
    local eventId = eventInfo.id;

    -- 检查是否需要闪烁
    if screenIcon == "activity" then
        -- 天空活动
        -- 如果当前的活动没有战斗信息才有效果
        local activityId = eventInfo["activity_id"];
        local activityData  = SkyActivityM.getActivityData(activityId);
        local combat        = activityData["combat"] or {};
        local status = ActivityM.getActivityStatus(ActivityM.getActivityInfo(activityId));

        if status == ACTIVITY_STATUS_START and #table.keys(combat) <= 0 then
            needAnimation = true;
        end
    elseif eventId == SCRAP_BONUS then
        -- 巨大的雕像
        local canGetNum = SkyScrapBonusM.getAvailableBonuNum();
        if canGetNum > 0 then
            needAnimation = true;
        end
    elseif eventId == SKY_TOWER then
        -- 永恒之塔
        local camp = ME.user.dbase:query("activedSkyGroup");
        if SkyTowerM.canFetchBonus(camp) == true then
            needAnimation = true;
        end
    end

    return needAnimation;
end

-- 节点是否处于冷却时间内
function UISkyCity:isPosInCoolTime(eventInfo)
    local eventId = eventInfo.id;
    local rule = SkyExploreM.query(eventId, "rule");

    if type(eventInfo["trigger_id"]) == "number" then
        if SkyExploreTriggerM.query(eventInfo["trigger_id"], "rule") == "ship_remains" and
            not NewAirShipM.hasOwnShip(eventInfo.ship_id) then
            return true;
        end
    elseif rule == "monster" or rule == "player" or rule == "boss" then
        if type(eventInfo["last_fail"]) == "number" and type(triggerId) ~= "number" then
            local remainTime = SkyExploreM.getFailResetTime(eventInfo);
            if type(remainTime) == "number" and remainTime > 0 then
                return true;
            end
        end
    elseif rule == "whirlpool" then
        local camp = ME.user.dbase:query("activedSkyGroup");
        if camp ~= eventInfo.camp then
            return true;
        end
    end

    return false;
end

-- 根据名称生成浮岛
function UISkyCity:initIsland(name, islandType)
    -- TODO:考虑进入游戏时，一次性缓存所有浮岛
    -- 获取浮岛节点
    local islandNode = cc.CSLoader:createNode("layout/sky_city/skyIslandTemplate.csb");
    local widget = replaceNodeWithWidget(islandNode);
    local child = widget:clone();
    child:setName("island");

    -- 获取配置
    local configs, islandType = SkyIslandM.initIslandByName(name, islandType);
    if not configs or type(configs) ~= "table" or #configs == 0 then
        return nil;
    end

    -- 生成浮岛
    for _, config in pairs(configs) do
        local node = findChildByName(child, config["node"]);
        node:setVisible(true);
        if cc.SpriteFrameCache:getInstance():getSpriteFrame(getskyIslandIconPath(config["icon"])) then
            node:loadTexture(getskyIslandIconPath(config["icon"]), 1);
        else
            cc.SpriteFrameCache:getInstance():addSpriteFrames(SKY_ISLAND_PLIST_PATH);
            node:loadTexture(getskyIslandIconPath(config["icon"]), 1);
        end

        -- 偏移微调
        local offset = config["offset"];
        node:setPosition(node:getPositionX() + offset[1], node:getPositionY() + offset[2]);

        -- 缩放调整
        local scale = config["scale"];
        if type(scale) == "number" then
            node:setScale(scale / 1000);
        end

        -- 缩放透明度
        local opacity = config["opacity"];
        if type(opacity) == "number" then
            node:setOpacity(opacity / 1000 * 255);
        end

        -- 根据层级相对位移
        local layer = config["layer"];
        performWithDelay(node, function()
            playFluctuateAnima(node, 1.5, 8);
        end, layer - 0.5);
    end

    return child, islandType;
end

-- 生成触发事件浮岛
function UISkyCity:initTriggerIsland(exploreInfo)
    local triggerId = exploreInfo["trigger_id"];
    local eventInfo = SkyExploreTriggerM.query(triggerId);
    local island;
    local iconImg;

    local islandType = eventInfo["island_type"];
    if eventInfo["rule"] == "ship_remains" then
        -- 飞艇残骸
        island, islandType = self:initIsland(eventInfo["island_config"], islandType);
        self:setInitIslandType(eventInfo, islandType);

        iconImg = findChildByName(island, "event_icon");
        iconImg:setVisible(true);
        local icon = SkyShipSkillM.query(NewAirShipM.query(exploreInfo["ship_id"], "physic_skill"), "icon");
        iconImg:loadTexture(getAirShipSkillIconPath(icon));
    else
        -- 触发事件浮岛
        island, islandType = self:initIsland(eventInfo["island_config"], islandType);
        self:setInitIslandType(eventInfo, islandType);

        iconImg = findChildByName(island, "event_icon");
        iconImg:setVisible(true);
        iconImg:loadTexture(getTriggerIconPath(eventInfo["icon"]));
    end

    return island;
end

-- 记录生成的浮岛类型
function UISkyCity:setInitIslandType(eventInfo, islandType)
    if not eventInfo["islandType"] then
        eventInfo["island_type"] = islandType;
    end
end

-- 执行选择选项结果处理
function UISkyCity:doChoiceOptionResult(para)
    local oldData       = self.eventsList[para["pos"]];
    local triggerId     = oldData["trigger_id"];
    local optionId      = para["option_id"];
    local questionId    = oldData["question_id"];
    local result        = para["result"];
    local optionInfo    = SkyExploreTriggerM.queryOption(optionId);
    local extraData     = optionInfo["extra_data"];

    self:initData();

    -- 检测是否要播放探索按钮提示光效
    self:checkSelectBtnTipEffect();

    -- 更新下浮岛节点
    self:updateEvents(para["pos"]);
    self:redrawFocus();
    self:redrawRadarInfo();
    self:redrawEnergy();

    -- 如果是飞艇残骸，那么调用其他接口处理
    if SkyExploreTriggerM.query(triggerId, "rule") == "ship_remains" then
        self:doShipRemainsResult(oldData, triggerId, optionId, result);
        return;
    end

    if self.eventsList[oldData["pos"]] == nil then
        self.exploreRecord = oldData;
        self.exploreRecord["disappear"] = true;
        self.exploreRecord["triggerBonus"] = result["bonus"];
    end

    -- self:redrawAirShip();

    -- 如果是特殊商店事件，则关闭商店界面和商品信息界面
    if SkyExploreTriggerM.query(triggerId, "rule") == "shop" then
        UIMgr.getCurrentScene():removeFormByName("UISkyCityExploreShop");
        UIMgr.getCurrentScene():removeFormByName("UISkyCityExploreShopGoodsInfo");
    end

    if result["combat"] or (result["random_combat"] and
        not result["random_combat"]["option_result_id"]) then
        -- 触发了战斗

        -- 有战斗信息，播放战斗
        local combatInfo = result["combat"] or result["random_combat"];
        local combatResult = combatInfo.result;
        local attackerInfo = combatInfo.attacker;
        local defenserInfo = combatInfo.defenser;
        local record = combatInfo.record;

        local function focusChanged()
            if combatResult == 0 then
                result["result"] = "combat";
            end
            self.focusChangedCallback = nil;

            -- 处理选择选项结果的奖励和提示语
            self:doChoiceOptionBonusAndTip(result, oldData, triggerId, optionId, questionId);
        end

        self.focusChangedCallback = focusChanged;

        -- 创建一个战场
        SkyCombatM.createBattle(attackerInfo, defenserInfo, combatResult, record, {});
        return;
    elseif type(extraData) == "table" then
        if extraData["rule"] == "portal" then
            -- 传送门效果

            -- 播放画面变暗的效果
            local imgNode = findChildByName(self.node, "black_mask");
            imgNode:setVisible(true);
            imgNode:setOpacity(0);

            -- 图片渐入
            local fadeIn = cc.FadeTo:create(0.8, 230);
            imgNode:runAction(cc.Sequence:create(fadeIn));

            -- 提供一个回调函数，在玩家点击了提示框的确定按钮之后调用
            local function callback()
                -- 淡出，并且最后设置黑色蒙版恢复透明度并且隐藏
                local fadeOut = cc.FadeTo:create(1.5, 0);
                local callFunc = cc.CallFunc:create(function()
                    imgNode:setVisible(false);
                    imgNode:setOpacity(255);
                end);
                imgNode:runAction(cc.Sequence:create(fadeOut, callFunc));
            end

            -- 弹出提示框
            local msg = optionInfo["bonus_desc"];
            -- 如果是字符串型的提示语配置，并且存在一样的公式时，认为是公式，调用公式获取提示语
            if type(msg) == "string" and FormulaM.getFormula(msg) then
                msg = FormulaM.invoke(msg, triggerId, optionId, questionId);
            end

            if extraData["effect"] == 1 then
                msg = msg .. "\n" .. string.format(getLocStr("explore_level_reduce_tip"), ME.user.dbase:query("explore_level", 0));
            else
                msg = msg .. "\n" .. string.format(getLocStr("explore_level_add_tip"), ME.user.dbase:query("explore_level", 0));
            end

            confirm2(CONFIRM_TYPE_OK, SkyExploreTriggerM.query(triggerId, "name"), msg, callback, nil, nil, nil,
                {["line_word_count"] = 19, ["hideBackBtn"] = true});
        elseif extraData["rule"] == "confirm" then
            if result["result"] ~= 1 then
                -- 如果结果不是成功，那么走正常流程弹出失败提示
                self:doChoiceOptionBonusAndTip(result, oldData, triggerId, optionId, questionId);
                return;
            end

            -- 弹出提示框
            local msg = optionInfo["bonus_desc"];
            -- 如果是字符串型的提示语配置，并且存在一样的公式时，认为是公式，调用公式获取提示语
            if type(msg) == "string" and FormulaM.getFormula(msg) then
                msg = FormulaM.invoke(msg, triggerId, optionId, questionId);
            end

            -- 弹出提示框
            confirm2(CONFIRM_TYPE_OK, SkyExploreTriggerM.query(triggerId, "name"), msg, callback, nil, nil, nil,
                {["line_word_count"] = 19, ["hideBackBtn"] = true});
        else
            -- 处理选择选项结果的奖励和提示语
            self:doChoiceOptionBonusAndTip(result, oldData, triggerId, optionId, questionId);
        end
    else
        -- 处理选择选项结果的奖励和提示语
        self:doChoiceOptionBonusAndTip(result, oldData, triggerId, optionId, questionId);
    end
end

-- 处理选择选项结果的奖励和提示语
function UISkyCity:doChoiceOptionBonusAndTip(result, oldData, triggerId, optionId, questionId)
    if result["result"] == 1 then
        -- 选择成功
        -- 弹出消耗提示语
        if type(oldData["cost"]) == "table" and
            type(oldData["cost"][optionId]) == "table" then
            for _, cost in ipairs(oldData["cost"][optionId]) do
                if cost[1] == 1 then
                    -- 道具类消耗
                    alert(ItemM.query(cost[2], "name") .. "-" .. tostring(cost[3]));
                elseif cost[1] == 2 then
                    -- 属性类消耗
                    alert(FieldsM.query(cost[2], "name") .. "-" .. tostring(cost[3]));
                end
            end
        end

        -- 弹出成功提示语
        local tips;
        local succTip = SkyExploreTriggerM.queryOption(optionId, "succ_tip") or {};

        -- 如果是字符串型的提示语配置，并且存在一样的公式时，认为是公式，调用公式获取提示语
        if type(succTip) == "string" and FormulaM.getFormula(succTip) then
            tips = FormulaM.invoke(succTip, triggerId, optionId, questionId);
        else
            tips = succTip;
        end

        if type(tips) == "string" then
            alert(tips);
        elseif type(tips) == "table" then
            for _, tipStr in ipairs(tips) do
                alert(tipStr);
            end
        end

        -- 弹出奖励提示
        local bonus = result["bonus"];
        if type(bonus) == "table" and #bonus > 0 then
            local title = SkyExploreTriggerM.query(triggerId, "name");

            local msg = SkyExploreTriggerM.queryOption(optionId, "bonus_desc");

            if type(msg) == "table" then
                -- 如果奖励提示的格式为table，那么说明有多种结果的条件，那么提示根据option_result_id获取
                local id = result["option_result_id"] or 1;
                msg = msg[id];
            elseif type(msg) == "string" and FormulaM.getFormula(msg) then
                -- 如果是字符串型的奖励提示配置，并且存在一样的公式时，认为是公式，调用公式获取奖励提示
                msg = FormulaM.invoke(msg, triggerId, optionId, questionId);
            end

            if type(questionId) == "number" then
                msg = SkyExploreTriggerM.queryQuestion(questionId, "bonus_desc");
                msg = getLocStr(msg);
            end

            -- 奖励提示框关闭回调
            local function callback()
                -- 播放奖励特效
                self:playSkyCityBonusEffect(bonus);
            end

            showBonusNotifyForm(title, bonus, nil, msg, nil, nil, nil, callback);
        else
            -- 没有弹出其他界面的话，直接处理下浮岛消失
            self:playIslandDisappear();
        end
    else
        -- 选择失败

        -- 弹出失败提示语
        local tips;
        local failTip = SkyExploreTriggerM.queryOption(optionId, "fail_tip") or {};

        failTip = failTip[result["result"]];
        -- 如果是字符串型的提示语配置，并且存在一样的公式时，认为是公式，调用公式获取提示语
        if type(failTip) == "string" and FormulaM.getFormula(failTip) then
            tips = FormulaM.invoke(failTip, triggerId, optionId, questionId);
        else
            tips = failTip;
        end

        if type(questionId) == "number" then
            tips = SkyExploreTriggerM.queryQuestion(questionId, "fail_tip");
            tips = getLocStr(tips);
        end

        local optionInfo    = SkyExploreTriggerM.queryOption(optionId);
        local extraData     = optionInfo["extra_data"];

        if type(extraData) == "table" and extraData["rule"] == "fail_config_and_tip" then
            -- 奖励提示框关闭回调
            local function callback()
                -- 处理下浮岛消失
                self:playIslandDisappear();
            end

            -- 弹出提示框
            confirm2(CONFIRM_TYPE_OK, SkyExploreTriggerM.query(triggerId, "name"), tips[1], callback, nil, nil, nil, {["line_word_count"] = 19});

            performWithDelay(self, function ()
                alert(tips[2]);
            end,0.2);
        else
            if type(tips) == "string" then
                alert(tips);
            elseif type(tips) == "table" then
                for _, tipStr in ipairs(tips) do
                    alert(tipStr);
                end
            end

            -- 没有弹出其他界面的话，直接处理下浮岛消失
            self:playIslandDisappear();
        end
    end
end

-- 处理飞艇残骸选择选项的结果
function UISkyCity:doShipRemainsResult(oldData, triggerId, optionId, result)
    local options = SkyExploreTriggerM.query(triggerId, "options");
    if table.indexOf(options, optionId) == 3 then
        -- 选择了第三项，默认是调查

        -- 弹出成功提示语
        local tips;
        local succTip = SkyExploreTriggerM.queryOption(optionId, "succ_tip") or {};

        -- 如果是字符串型的提示语配置，并且存在一样的公式时，认为是公式，调用公式获取提示语
        if type(succTip) == "string" and FormulaM.getFormula(succTip) then
            tips = FormulaM.invoke(succTip, triggerId, optionId, questionId);
        else
            tips = succTip;
        end

        if type(tips) == "string" then
            alert(tips);
        elseif type(tips) == "table" then
            for _, tipStr in ipairs(tips) do
                alert(tipStr);
            end
        end

        -- 播放飞艇残骸消失的效果
        self:playDisappearEffect();

        -- 1秒之后淡出结束了，重绘下飞艇
        performWithDelay(self, function ()
            self:redrawAirShip();
        end, 1);

        local airShip = findChildByName(self.node, "CT/air_ship");
        local effectNode = self.node:getChildByName("effect");
        local worldPos = airShip:convertToWorldSpaceAR(cc.p(0, 0));
        local fromPos = effectNode:convertToNodeSpace(worldPos);

        -- 播放奖励飞入效果
        self:playSkyCityBonusEffect(result["bonus"], fromPos, true);
    else
        self:redrawAirShip();
        -- 处理选择选项结果的奖励和提示语
        self:doChoiceOptionBonusAndTip(result, oldData, triggerId, optionId, questionId);
    end
end

-- 播放残骸消失效果
function UISkyCity:playDisappearEffect()
    -- 飞艇残骸逐渐消失
    local airShip = findChildByName(self.node, "CT/air_ship");
    local island = findChildByName(airShip, "island");
    if island ==  nil then
        island = findChildByName(airShip, "airship");
    end

    -- 如果要播放消失效果的浮岛/飞艇/残骸已经不存在了，那么直接返回
    if not island then
        return
    end

    -- 由于飞艇残骸使用的是csd，所以这里需要遍历所以的子节点去做淡出效果
    local children = island:getChildren();
    local len = table.getn(children)
    for i = 1, len do
        local child = children[i];

        local fadeOut = cc.FadeOut:create(1);
        child:runAction(cc.Sequence:create(fadeOut));
    end

    -- 自己也做下淡出，因为目前还有一些资源用的是静态图
    local fadeOut = cc.FadeOut:create(1);
    island:runAction(cc.Sequence:create(fadeOut));

    -- 缩小放大效果
    local reticleNode = findChildByName(self.node, "CT/reticle_node");
    local airshipInfoNode = findChildByName(self.node, "CT/airship_info");
    local airshipBtnNode = findChildByName(self.node, "CT/airship_btn");

    for index, node in ipairs({reticleNode, airshipInfoNode, airshipBtnNode}) do
        local scaleTo1 = cc.ScaleTo:create(0.1, 0.7);
        local scaleTo2 = cc.ScaleTo:create(0.2, 1.05);
        local scaleTo3 = cc.ScaleTo:create(0.15, 0.4);
        local callFunc = cc.CallFunc:create(function ()
            node:setScale(1);
            node:setVisible(false);
        end)

        node:runAction(cc.Sequence:create(scaleTo1, scaleTo2, scaleTo3, callFunc));
    end
end

-- 创建时空裂痕浮岛
function UISkyCity:createWhirlpoolIsland(camp)
    local whirlpoolNode = cc.CSLoader:createNode("layout/sky_city/WhirlpoolItem.csb");

    local centerImg = findChildByName(whirlpoolNode, "center");
    local centerIcon = getWhirlpoolIcon("center_" .. camp);
    centerImg:loadTexture(centerIcon);
    for index = 1, 6 do
        local scrapNode = findChildByName(whirlpoolNode, "scrap_node" .. index);
        self:WhirlpoolScrapRotation(scrapNode);
    end

    return whirlpoolNode;
end

-- 转动动画
function UISkyCity:WhirlpoolScrapRotation(scrapNode)
    -- 先暂停下所有准心图片的行为
    scrapNode:stopAllActions();

    local delay = cc.DelayTime:create(0.1);
    local rotateRight = cc.RotateBy:create(5, 90);
    local rotateLeft = cc.RotateBy:create(5, -90);

    local action = cc.Sequence:create(rotateRight, delay, rotateLeft);

    scrapNode:runAction(cc.RepeatForever:create(action));
end

-- 飞艇残骸出现的特效
function UISkyCity:playShipRemainsAppear()
    local exploreInfo = self.eventsList[self.selectPos];

    if not exploreInfo or
        not exploreInfo["trigger_id"] or
        not exploreInfo["ship_id"] then
        return;
    end

    local airShip = findChildByName(self.node, "CT/air_ship");
    airShip:removeAllChildren();
    local shipNode = createAirShip(airShip, exploreInfo["enemy"]["ship_id"]);
    shipNode:setScale(0.8);

    -- 飞艇震动
    playShakeEffect(airShip, 0.04, 4, 6);

    local function frameEventFunc(frame)
        if frame.getEvent and frame:getEvent() == "pilor" then
            local index = frame:getFrameIndex();

            -- 光效播放完了，显示准心，目标信息，按钮节点
            if index == 17 then
                local form = UIMgr.getCurrentScene():getFormByName("UISkyCity");

                if not form then
                    return;
                end

                -- 准心光效
                form:playReticleEffect();

                -- 飞船信息节点
                local airShipInfoNode = findChildByName(form.node, "CT/airship_info");
                -- 飞船按钮节点
                local airShipBtn = findChildByName(form.node, "CT/airship_btn");
                airShipInfoNode:setVisible(true);
                airShipBtn:setVisible(true);

                -- 播放下目标浮岛/飞艇出现的特效
                local btnDoEvent = findChildByName(airShipBtn, "do_event/btn_do_event");
                local btnInfo = findChildByName(airShipBtn, "info/btn_info");
                form:playAirshipBtnEffect(btnDoEvent);
                form:playAirshipBtnEffect(btnInfo);
            end
        end
    end

    performWithDelay(self, function ()
        -- 爆炸光效
        playEffect(airShip, 800, -30, 30, nil, 2);
    end, 0.6);
    performWithDelay(self, function ()
        -- 爆炸光效
        playEffect(airShip, 800, 30, -30, nil, 2);
    end, 0.75);
    performWithDelay(self, function ()
        -- 重绘下飞艇或者浮岛，把临时的飞艇改为真正的飞艇残骸
        self:redrawShipOrIslandIcon(self.eventsList[self.selectPos] or exploreInfo);

        -- 残骸主体需要有个放大缩小的效果
        local island = findChildByName(airShip, "island");
        if island then
            local distantView = findChildByName(island, "distant_view");
            local eventIcon = findChildByName(island, "event_icon");
            local closeShot = findChildByName(island, "close_shot");
            if distantView and eventIcon and closeShot then
                for index, node in ipairs({distantView, closeShot, eventIcon}) do
                    local scale = node:getScale();
                    node:setScale(0.01);

                    local delayTime = cc.DelayTime:create(0.15 + 0.1 * index);
                    local scaleTo1 = cc.ScaleTo:create(0.25, 1.3 * scale);
                    local scaleTo2 = cc.ScaleTo:create(0.1, scale);

                    node:runAction(cc.Sequence:create(delayTime, scaleTo1, scaleTo2));
                end
            end
        end

        -- 爆炸光效
        playEffect(airShip, 800, nil, nil, frameEventFunc, 2);
    end, 0.9);
    performWithDelay(self, function ()
        -- 爆炸光效
        playEffect(airShip, 800, -30, -30, nil, 1.6);
    end, 1);
end

-- 浮岛消失特效
function UISkyCity:playIslandDisappear()
    if self.exploreRecord == nil then
        return;
    end

    if self.focusChangedCallback then
        return;
    end

    -- 需要播放消失时候才消失
    if self.exploreRecord["disappear"] ~= true then
        return;
    end

    -- 如果时空裂痕需要提示
    if SkyExploreM.query(self.exploreRecord["id"], "rule") == "whirlpool" then
        alert(getLocStr("you_close_whirlpool"));
    end

    if self.exploreRecord["triggerBonus"] and #self.exploreRecord["triggerBonus"] > 0 then
        local function frameEventFunc(frame)
            if frame.getEvent and frame:getEvent() == "pilor" then
                local fIndex = frame:getFrameIndex();

                if fIndex == 15 then
                    self:playDisappearEffect();
                end
            end
        end

        -- 播放光效
        local islandImg = findChildByName(self.node, "CT/air_ship");
        playEffect(islandImg, 1814, 0, 0, frameEventFunc);
    else
        self:playDisappearEffect();
    end

    -- 播放消失音效
    AudioM.playFx("sky_island");

    -- 当前信息节点消失，所以选中要重绘下
    self.selectPos = 0;

    -- 重新绘制下焦点
    self:redrawFocus();

    -- 播放消失
    self.exploreRecord = nil;
end

-- 设置信息按钮和袭击按钮不可用
function UISkyCity:setEventBtnDisable(isDisable)
    local doEventBtn = findChildByName(self.node, "CT/airship_btn/do_event/btn_do_event");
    doEventBtn:setTouchEnabled(not isDisable);

    local btnInfo = findChildByName(self.node, "CT/airship_btn/info/btn_info");
    btnInfo:setTouchEnabled(not isDisable);

    -- 点击飞艇或者浮岛
    local airShip = findChildByName(self.node, "CT/airship_panel");
    airShip:setTouchEnabled(not isDisable);
end

-- 打开天空活动界面
function UISkyCity:openSkyActivityInfo(activityId)
    require "game/ui/form/sky_city/UISkyActivity"
    local uiForm = UISkyActivity.create(activityId);
    UIMgr.getCurrentScene():addForm(uiForm);
end
